Browse Source

update src/project_layouts

master
Julian Noble 1 year ago
parent
commit
837631fa0d
  1. 418
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  2. 1028
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/commandstack-0.3.tm
  3. 21
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm
  4. 12822
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/metaface-1.2.5.tm
  5. 705
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/modpod-0.1.0.tm
  6. 1894
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/natsort-0.1.1.5.tm
  7. 1288
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm
  8. 1508
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm
  9. 3
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  10. 65
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  11. 972
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/config-0.1.tm
  12. 5
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  13. 20
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  14. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/buildsuite-0.1.0.tm
  15. 8
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/debug-0.1.0.tm
  16. 6
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  17. 170
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  18. 38
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm
  19. 7
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/templates/utility/a b/tcltest.bat
  20. 327
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mod-0.1.tm
  21. 21
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  22. 240
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  23. 478
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkapp-0.1.tm
  24. 114
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  25. BIN
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.1.tm
  26. BIN
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.3.tm
  27. 7408
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.1.tm
  28. 160
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/tomlish-1.1.2.tm
  29. 6002
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/tomlish-1.1.3.tm
  30. 418
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  31. 1028
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/commandstack-0.3.tm
  32. 21
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm
  33. 12822
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/metaface-1.2.5.tm
  34. 1288
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm
  35. 1508
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm
  36. 3
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  37. 65
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  38. 972
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/config-0.1.tm
  39. 5
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  40. 20
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  41. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/buildsuite-0.1.0.tm
  42. 8
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/debug-0.1.0.tm
  43. 6
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  44. 170
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  45. 38
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm
  46. 327
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mod-0.1.tm
  47. 21
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  48. 240
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  49. 478
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkapp-0.1.tm
  50. 114
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  51. BIN
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.1.tm
  52. BIN
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.3.tm
  53. 160
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/tomlish-1.1.2.tm
  54. 6002
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/tomlish-1.1.3.tm
  55. 418
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl
  56. 259
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/argp-0.2.tm
  57. 568
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/argparsingtest-0.1.0.tm
  58. 514
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/commandstack-0.3.tm
  59. 306
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/debug-1.0.6.tm
  60. 29
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm
  61. 74
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fileutil/paths-1.tm
  62. 504
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm
  63. 2714
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/flagfilter-0.3.tm
  64. 325
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/funcl-0.1.tm
  65. 1297
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/logger-0.9.5.tm
  66. 6411
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/metaface-1.2.5.tm
  67. 705
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/modpod-0.1.0.tm
  68. 37
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/modpod-0.1.2.tm
  69. 33
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm
  70. 2711
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  71. 1285
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/pattern-1.2.4.tm
  72. 645
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm
  73. 2590
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patternlib-1.2.6.tm
  74. 754
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm
  75. 1311
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/promise-1.2.0.tm
  76. 8187
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk-0.1.tm
  77. 290
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  78. 2498
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  79. 5395
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  80. 48
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/assertion-0.1.0.tm
  81. 60
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap-0.1.0.tm
  82. 4
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap/handlers/caphandler-0.1.0.tm
  83. 158
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  84. 591
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  85. 487
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/config-0.1.tm
  86. 1586
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  87. 1
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/docgen-0.1.0.tm
  88. 403
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/du-0.1.0.tm
  89. 219
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm
  90. 2401
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  91. 172
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  92. 376
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  93. 1128
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.tm
  94. 2
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/buildsuite-0.1.0.tm
  95. 8
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/debug-0.1.0.tm
  96. 42
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm
  97. 118
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm
  98. 43
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  99. 126
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  100. 253
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  101. Some files were not shown because too many files have changed in this diff Show More

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

@ -2,12 +2,15 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1}
}
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
puts " Punk Boot" puts " Punk Boot"
puts $hashline\n puts $hashline\n
package prefer latest package prefer latest
lassign [split [info tclversion] .] tclmajorv tclminorv lassign [split [info tclversion] .] tclmajorv tclminorv
global A ;#UI Ansi code array global A ;#UI Ansi code array
@ -104,7 +107,7 @@ namespace eval ::punkboot::lib {
} }
} }
return [join $newparts .] return [join $newparts .]
} }
proc tm_version_required_canonical {versionspec} { proc tm_version_required_canonical {versionspec} {
#also trim leading zero from any dottedpart? #also trim leading zero from any dottedpart?
#Tcl *allows* leading zeros in any of the dotted parts - but they are not significant. #Tcl *allows* leading zeros in any of the dotted parts - but they are not significant.
@ -112,10 +115,10 @@ namespace eval ::punkboot::lib {
#also 1b3 == 1b0003 #also 1b3 == 1b0003
if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version
set errmsg "punkboot::lib::tm_version_required_canonical - invalid version specification" set errmsg "punkboot::lib::tm_version_required_canonical - invalid version specification"
if {[string first - $versionspec] < 0} { if {[string first - $versionspec] < 0} {
#no dash #no dash
#looks like a minbounded version (ie a single version with no dash) convert to min-max form #looks like a minbounded version (ie a single version with no dash) convert to min-max form
set from $versionspec set from $versionspec
if {![::punkboot::lib::tm_version_isvalid $from]} { if {![::punkboot::lib::tm_version_isvalid $from]} {
error "$errmsg '$versionpec'" error "$errmsg '$versionpec'"
@ -127,7 +130,7 @@ namespace eval ::punkboot::lib {
error "$errmsg '$versionspec'" error "$errmsg '$versionspec'"
} }
} else { } else {
# min- or min-max # min- or min-max
#validation and canonicalisation (strip leading zeroes from each segment, including either side of a or b) #validation and canonicalisation (strip leading zeroes from each segment, including either side of a or b)
set parts [split $versionspec -] ;#we expect only 2 parts set parts [split $versionspec -] ;#we expect only 2 parts
lassign $parts from to lassign $parts from to
@ -162,18 +165,18 @@ if {"::try" ni [info commands ::try]} {
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#Module loading from src/bootsupport or [pwd]/modules if pwd is a 'src' folder #Module loading from src/bootsupport or [pwd]/modules if pwd is a 'src' folder
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#If there is a folder under the current directory, in the subpath src/bootsupport/modules which contains .tm files #If there is a folder under the current directory, in the subpath src/bootsupport/modules which contains .tm files
# - then it will attempt to preference these modules # - then it will attempt to preference these modules
# This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script
# and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables
set startdir [pwd] set startdir [pwd]
#we are focussed on pure-tcl libs/modules in bootsupport for now. #we are focussed on pure-tcl libs/modules in bootsupport for now.
#There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc
#REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries
# - we need to support that without binary downloads from repos unless the user explicitly asks for that. # - we need to support that without binary downloads from repos unless the user explicitly asks for that.
# - They may already be available in the vfs (or pointed to package paths) of the running executable. # - They may already be available in the vfs (or pointed to package paths) of the running executable.
# - todo: some user prompting regarding installs with platform-appropriate package managers # - todo: some user prompting regarding installs with platform-appropriate package managers
# - todo: some user prompting regarding building accelerators from source. # - todo: some user prompting regarding building accelerators from source.
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
set bootsupport_module_paths [list] set bootsupport_module_paths [list]
@ -209,7 +212,7 @@ if {[file tail $startdir] eq "src"} {
#todo - other src 'module' dirs.. #todo - other src 'module' dirs..
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] { foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] {
if {[file exists $p]} { if {[file exists $p]} {
lappend sourcesupport_module_paths $p lappend sourcesupport_module_paths $p
} }
} }
# -- -- -- # -- -- --
@ -219,7 +222,7 @@ if {[file tail $startdir] eq "src"} {
} }
} }
# -- -- -- # -- -- --
foreach p [list {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] { foreach p [list {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] {
if {[file exists $p]} { if {[file exists $p]} {
set sourcesupport_paths_exist 1 set sourcesupport_paths_exist 1
@ -228,7 +231,7 @@ if {[file tail $startdir] eq "src"} {
} }
if {$sourcesupport_paths_exist} { if {$sourcesupport_paths_exist} {
#launch from <projectdir/src is also likely to be common #launch from <projectdir/src is also likely to be common
# but we need to be loud about what's going on. # but we need to be loud about what's going on.
puts stderr "------------------------------------------------------------------" puts stderr "------------------------------------------------------------------"
puts stderr "Launched from within a folder ending in 'src'" puts stderr "Launched from within a folder ending in 'src'"
@ -238,7 +241,7 @@ if {[file tail $startdir] eq "src"} {
} }
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
set package_paths_modified 0 set package_paths_modified 0
if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
set original_tm_list [tcl::tm::list] set original_tm_list [tcl::tm::list]
tcl::tm::remove {*}$original_tm_list tcl::tm::remove {*}$original_tm_list
@ -254,7 +257,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
} }
} }
set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk] ;#packages we set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk]
if {$support_contents_exist} { if {$support_contents_exist} {
#only forget all *unloaded* package names #only forget all *unloaded* package names
foreach pkg [package names] { foreach pkg [package names] {
@ -270,9 +273,9 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
package forget $pkg package forget $pkg
} }
} }
#tcl::tm::add {*}$original_tm_list {*}$bootsupport_module_paths {*}$sourcesupport_module_paths #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] #set ::auto_path [list {*}$original_auto_path {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths] set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
} }
puts "----> auto_path $::auto_path" puts "----> auto_path $::auto_path"
@ -281,18 +284,19 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
#package require Thread #package require Thread
# - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly.
# tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list
#These are strong dependencies #These are strong dependencies
package forget punk::mix package forget punk::mix
package forget punk::repo package forget punk::repo
package forget punkcheck package forget punkcheck
package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies
package require punk::mix package require punk::mix
package require punkcheck package require punkcheck
package require punk::lib package require punk::lib
package require punk::args
package require punk::ansi
set package_paths_modified 1 set package_paths_modified 1
@ -302,11 +306,12 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
set ::punkboot::pkg_requirements_found [list] set ::punkboot::pkg_requirements_found [list]
#we will treat 'package require <mver>.<etc>' (minbounded) as <mver>.<etc>-<mver+1> ie explicitly convert to corresponding bounded form #we will treat 'package require <mver>.<etc>' (minbounded) as <mver>.<etc>-<mver+1> ie explicitly convert to corresponding bounded form
#put some with leading zeros to test normalisation #put some with leading zeros to test normalisation
set ::punkboot::bootsupport_requirements [dict create\ set ::punkboot::bootsupport_requirements [dict create\
punk::repo [list version "00.01.01-"]\ punk::repo [list version "00.01.01-"]\
punk::mix [list version ""]\ punk::mix [list version ""]\
punk::ansi [list]\ punk::ansi [list]\
punk::args [list]\
overtype [list version "1.6.5-"]\ overtype [list version "1.6.5-"]\
punkcheck [list]\ punkcheck [list]\
fauxlink [list version "0.1.1-"]\ fauxlink [list version "0.1.1-"]\
@ -322,7 +327,7 @@ dict for {pkg pkginfo} $::punkboot::bootsupport_requirements {
if {![catch {::punkboot::lib::tm_version_required_canonical $ver} canonical]} { if {![catch {::punkboot::lib::tm_version_required_canonical $ver} canonical]} {
if {$canonical ne $ver} { if {$canonical ne $ver} {
dict set pkginfo version $canonical ;# plain ver mapped to min-max. min- and min-max and empty left as is dict set pkginfo version $canonical ;# plain ver mapped to min-max. min- and min-max and empty left as is
dict set ::punkboot::bootsupport_requirements $pkg $pkginfo dict set ::punkboot::bootsupport_requirements $pkg $pkginfo
} }
} else { } else {
puts stderr "punkboot::bootsupport_requirements - package $pkg has invalid version specification '$ver'" puts stderr "punkboot::bootsupport_requirements - package $pkg has invalid version specification '$ver'"
@ -331,9 +336,9 @@ dict for {pkg pkginfo} $::punkboot::bootsupport_requirements {
} else { } else {
#make sure each has a blank version entry if nothing was there. #make sure each has a blank version entry if nothing was there.
dict set pkginfo version "" dict set pkginfo version ""
dict set ::punkboot::bootsupport_requirements $pkg $pkginfo dict set ::punkboot::bootsupport_requirements $pkg $pkginfo
} }
} }
#Assert - our bootsupport_requirement version numbers should now be either empty or of the form min- or min-max #Assert - our bootsupport_requirement version numbers should now be either empty or of the form min- or min-max
#dict for {k v} $::punkboot::bootsupport_requirements { #dict for {k v} $::punkboot::bootsupport_requirements {
# puts "- $k $v" # puts "- $k $v"
@ -356,7 +361,7 @@ set ::punkboot::bootsupport_recommended [dict create\
# create an interp in which we hijack package command # create an interp in which we hijack package command
# This allows us to auto-gather some dependencies (not necessarily all and not necessarily strictly required) # This allows us to auto-gather some dependencies (not necessarily all and not necessarily strictly required)
# Note: even in a separate interp we could still possibly get side-effects if a package has compiled components - REVIEW # Note: even in a separate interp we could still possibly get side-effects if a package has compiled components - REVIEW
# Hopefully the only side-effect is that a subsequent load of the package will be faster... # Hopefully the only side-effect is that a subsequent load of the package will be faster...
# (punk boot is intended to operate without compiled components - but some could be pulled in by tcl modules if they're found) # (punk boot is intended to operate without compiled components - but some could be pulled in by tcl modules if they're found)
# (tcllibc is also highly desirable as the performance impact when not available can be dramatic.) # (tcllibc is also highly desirable as the performance impact when not available can be dramatic.)
# ... but if the binary is loaded with a different path name when we come to actually use it - there could be issues. # ... but if the binary is loaded with a different path name when we come to actually use it - there could be issues.
@ -378,7 +383,7 @@ proc ::punkboot::check_package_availability {args} {
#best effort at auto-determinining packages required (dependencies) based on top-level packages in the list. #best effort at auto-determinining packages required (dependencies) based on top-level packages in the list.
#Without fully parsing the package-loading Tcl scripts and examining all side-effects (an unlikely capability), #Without fully parsing the package-loading Tcl scripts and examining all side-effects (an unlikely capability),
# this is not going to be as accurate as the package developer providing a definitive list of which packages are required and which are optional. # this is not going to be as accurate as the package developer providing a definitive list of which packages are required and which are optional.
# 'optionality' is a contextual concept anyway depending on how the package is intended to be used. # 'optionality' is a contextual concept anyway depending on how the package is intended to be used.
# The package developer may consider a feature optional - but it may not be optional in a particular usecase. # The package developer may consider a feature optional - but it may not be optional in a particular usecase.
set bootsupport_requirements [lindex $args end] set bootsupport_requirements [lindex $args end]
@ -484,7 +489,7 @@ proc ::punkboot::check_package_availability {args} {
#should still distinguish: {pkgname {}} -valid vs {pkgname {{}}} due to empty string supplied in call - invalid - but leave for underlying package command to error on #should still distinguish: {pkgname {}} -valid vs {pkgname {{}}} due to empty string supplied in call - invalid - but leave for underlying package command to error on
set pkgrequest [list $pkgname $requirements_list] set pkgrequest [list $pkgname $requirements_list]
if {$pkgrequest ni $::test::pkg_requested} { if {$pkgrequest ni $::test::pkg_requested} {
lappend ::test::pkg_requested $pkgrequest lappend ::test::pkg_requested $pkgrequest
} }
# -- -- --- --- --- --- --- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- -- --- --- --- --- --- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
@ -507,13 +512,13 @@ proc ::punkboot::check_package_availability {args} {
} }
if {[llength $::test::pkg_stack]} { if {[llength $::test::pkg_stack]} {
set caller [lindex $::test::pkg_stack end] set caller [lindex $::test::pkg_stack end]
set required_by [dict get $pinfo required_by] set required_by [dict get $pinfo required_by]
if {$caller ni $required_by} { if {$caller ni $required_by} {
lappend required_by $caller lappend required_by $caller
} }
dict set pinfo required_by $required_by dict set pinfo required_by $required_by
} }
lappend ::test::pkg_stack $pkgname lappend ::test::pkg_stack $pkgname
#At this point we could short circuit if we've already classified this package/requirements combo as missing/broken from a previous require #At this point we could short circuit if we've already classified this package/requirements combo as missing/broken from a previous require
#review - there is some chance the exact pkg/requirements combo may succeed after an earlier failure if some package adjusted search paths.. #review - there is some chance the exact pkg/requirements combo may succeed after an earlier failure if some package adjusted search paths..
@ -527,23 +532,23 @@ proc ::punkboot::check_package_availability {args} {
#use our normalised requirements instead of original args #use our normalised requirements instead of original args
#if {[catch [list ::package_orig {*}$args] result]} {} #if {[catch [list ::package_orig {*}$args] result]} {}
if {[catch [list ::package_orig require $pkgname {*}$requirements_list] result]} { if {[catch [list ::package_orig require $pkgname {*}$requirements_list] result]} {
dict set pinfo testerror $result dict set pinfo testerror $result
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " \x1b\[93m$result\x1b\[m"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
#to determine the version that we attempted to load, #to determine the version that we attempted to load,
#- we need to look at 'pkg versions' vs -exact / ver / ver-ver (using package vsatisfies) #- we need to look at 'pkg versions' vs -exact / ver / ver-ver (using package vsatisfies)
if {![llength $versions]} { if {![llength $versions]} {
#no versions *and* we had an error - missing is our best guess. review. #no versions *and* we had an error - missing is our best guess. review.
#'package versions Tcl' never shows any results #'package versions Tcl' never shows any results
#so requests for old versions will show as missing not broken. #so requests for old versions will show as missing not broken.
#This is probably better anyway. #This is probably better anyway.
if {$pkgrequest ni $::test::pkg_missing} { if {$pkgrequest ni $::test::pkg_missing} {
@ -572,21 +577,21 @@ proc ::punkboot::check_package_availability {args} {
lappend selectable_versions $v lappend selectable_versions $v
} }
} else { } else {
#we are operating under 'package prefer' = latest #we are operating under 'package prefer' = latest
set selectable_versions $ordered_versions set selectable_versions $ordered_versions
} }
if {[llength $requirements_list]} { if {[llength $requirements_list]} {
#add one or no entry for each requirement. #add one or no entry for each requirement.
#pick highest at end #pick highest at end
set satisfiers [list] set satisfiers [list]
foreach requirement $requirements_list { foreach requirement $requirements_list {
foreach ver [lreverse $selectable_versions] { foreach ver [lreverse $selectable_versions] {
if {[package vsatisfies $ver $requirement]} { if {[package vsatisfies $ver $requirement]} {
lappend satisfiers $ver lappend satisfiers $ver
break break
} }
} }
} }
if {[llength $satisfiers]} { if {[llength $satisfiers]} {
set satisfiers [lsort -command {::package_orig vcompare} $satisfiers] set satisfiers [lsort -command {::package_orig vcompare} $satisfiers]
@ -622,7 +627,7 @@ proc ::punkboot::check_package_availability {args} {
if {![catch {::package_orig files Tcl} ]} { if {![catch {::package_orig files Tcl} ]} {
#tcl9 (also some 8.6/8.7) has 'package files' subcommand. #tcl9 (also some 8.6/8.7) has 'package files' subcommand.
#unfortunately, in some cases (e.g md5 when no accelerators available) this can be a huge list (1000+) showing all scanned pkgIndex.tcl files from unrelated packages. #unfortunately, in some cases (e.g md5 when no accelerators available) this can be a huge list (1000+) showing all scanned pkgIndex.tcl files from unrelated packages.
#We expect this to be fixed - but early Tcl9 (and some 8.6/8.7) versions may persist and have this behaviour #We expect this to be fixed - but early Tcl9 (and some 8.6/8.7) versions may persist and have this behaviour
#see: https://core.tcl-lang.org/tcl/tktview/209fd9adce #see: https://core.tcl-lang.org/tcl/tktview/209fd9adce
set all_files [::package_orig files $pkgname] set all_files [::package_orig files $pkgname]
#some arbitrary threshold? REVIEW #some arbitrary threshold? REVIEW
@ -637,7 +642,7 @@ proc ::punkboot::check_package_availability {args} {
dict set pinfo packagefiles {} ;#default dict set pinfo packagefiles {} ;#default
#there are all sorts of scripts, so this is not predictably structured #there are all sorts of scripts, so this is not predictably structured
#e.g using things like apply #e.g using things like apply
#we will attempt to get a trailing source .. <file> #we will attempt to get a trailing source .. <file>
set parts [split [string trim $ifneeded_script] {;}] set parts [split [string trim $ifneeded_script] {;}]
set trimparts [list] set trimparts [list]
foreach p $parts { foreach p $parts {
@ -648,7 +653,7 @@ proc ::punkboot::check_package_availability {args} {
if {$last_with_text ne "" && [regexp -- {\S+$} $last_with_text lastword]} { if {$last_with_text ne "" && [regexp -- {\S+$} $last_with_text lastword]} {
#if it's a file or dir - close enough (?) #if it's a file or dir - close enough (?)
#e.g tcllibc uses apply and the last entry is actuall a folder used to find the file.. #e.g tcllibc uses apply and the last entry is actuall a folder used to find the file..
#we aren't brave enough to try to work out the actual file(s) #we aren't brave enough to try to work out the actual file(s)
if {[file exists $lastword]} { if {[file exists $lastword]} {
dict set pinfo packagefiles $lastword dict set pinfo packagefiles $lastword
} }
@ -662,10 +667,10 @@ proc ::punkboot::check_package_availability {args} {
return [uplevel 1 [list ::package_orig {*}$args]] return [uplevel 1 [list ::package_orig {*}$args]]
} }
} }
set ::test::pkg_stack [list] set ::test::pkg_stack [list]
catch {::package_orig require zzz-non-existant} ;#scan so we get 'package versions' results catch {::package_orig require zzz-non-existant} ;#scan so we get 'package versions' results
dict for {pkg pkgdict} $::test::bootsupport_requirements { dict for {pkg pkgdict} $::test::bootsupport_requirements {
#set nsquals [namespace qualifiers $pkg] #set nsquals [namespace qualifiers $pkg]
#if {$nsquals ne ""} { #if {$nsquals ne ""} {
# catch {::package_orig require ${nsquals}::zzz-non-existant} ;#force scan of every level encountered # catch {::package_orig require ${nsquals}::zzz-non-existant} ;#force scan of every level encountered
@ -690,7 +695,7 @@ proc ::punkboot::check_package_availability {args} {
# set ver [package provide $pkg] # set ver [package provide $pkg]
# if {$ver eq ""} { # if {$ver eq ""} {
# #puts stderr "missing pkg: $pkg" # #puts stderr "missing pkg: $pkg"
# lappend ::test::pkg_missing $pkg # lappend ::test::pkg_missing $pkg
# } else { # } else {
# if {[string tolower $pkg] eq "tcl"} { # if {[string tolower $pkg] eq "tcl"} {
# #ignore # #ignore
@ -1180,17 +1185,17 @@ if {$::punkboot::command eq "check"} {
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
foreach fld [tcl::tm::list] { foreach fld [tcl::tm::list] {
if {[file exists $fld]} { if {[file exists $fld]} {
puts stdout " $fld" puts stdout " $fld"
} else { } else {
puts stdout " $fld (not present)" puts stdout " $fld (not present)"
} }
} }
puts stdout "- auto_path" puts stdout "- auto_path"
foreach fld $::auto_path { foreach fld $::auto_path {
if {[file exists $fld]} { if {[file exists $fld]} {
puts stdout " $fld" puts stdout " $fld"
} else { } else {
puts stdout " $fld (not present)" puts stdout " $fld (not present)"
} }
} }
flush stdout flush stdout
@ -1283,22 +1288,22 @@ if {$::punkboot::command eq "info"} {
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
puts stdout "- vendorlib folders: ([llength $vendorlibfolders])" puts stdout "- vendorlib folders: ([llength $vendorlibfolders])"
foreach fld $vendorlibfolders { foreach fld $vendorlibfolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
puts stdout "- vendormodule folders: ([llength $vendormodulefolders])" puts stdout "- vendormodule folders: ([llength $vendormodulefolders])"
foreach fld $vendormodulefolders { foreach fld $vendormodulefolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot] set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot]
puts stdout "- source module paths: [llength $source_module_folderlist]" puts stdout "- source module paths: [llength $source_module_folderlist]"
foreach fld $source_module_folderlist { foreach fld $source_module_folderlist {
puts stdout " $fld" puts stdout " $fld"
} }
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*] set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib lappend projectlibfolders lib
puts stdout "- source libary paths: [llength $projectlibfolders]" puts stdout "- source libary paths: [llength $projectlibfolders]"
foreach fld $projectlibfolders { foreach fld $projectlibfolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} { if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} {
set vc "fossil" set vc "fossil"
@ -1389,10 +1394,9 @@ if {$::punkboot::command eq "vendorupdate"} {
#todo vendor/lib #todo vendor/lib
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*] set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*]
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
lappend vendormodulefolders vendormodules #lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders { foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} {
lassign [split $vf _] _vm tclx lassign [split $vf _] _vm tclx
if {$tclx ne ""} { if {$tclx ne ""} {
set which _$tclx set which _$tclx
@ -1481,7 +1485,6 @@ if {$::punkboot::command eq "vendorupdate"} {
} else { } else {
puts stderr "No config at $vendor_config - nothing configured to update" puts stderr "No config at $vendor_config - nothing configured to update"
} }
}
} }
} }
@ -1508,105 +1511,102 @@ if {$::punkboot::command eq "bootsupport"} {
set bootsupport_modules [list] ;#variable populated by include_modules.config file - review set bootsupport_modules [list] ;#variable populated by include_modules.config file - review
set sourcefolder $projectroot/src set sourcefolder $projectroot/src
set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules_tcl*] set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules modules_tcl*]
lappend bootmodulefolders modules
foreach bm $bootmodulefolders { foreach bm $bootmodulefolders {
if {[file exists $sourcefolder/bootsupport/$bm]} { lassign [split $bm _] _bm tclx
lassign [split $bm _] _bm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
set which ""
}
set bootsupport_config $projectroot/src/bootsupport/modules$which/include_modules.config ;#
if {[file exists $bootsupport_config]} {
set targetroot $projectroot/src/bootsupport/modules$which
source $bootsupport_config ;#populate $bootsupport_modules with project-specific list
if {![llength $bootsupport_modules]} {
puts stderr "bootsupport/modules$which - No local bootsupport modules configured for updating"
} else { } else {
set which ""
}
set bootsupport_config $projectroot/src/bootsupport/modules$which/include_modules.config ;#
if {[file exists $bootsupport_config]} {
set targetroot $projectroot/src/bootsupport/modules$which
source $bootsupport_config ;#populate $bootsupport_modules with project-specific list
if {![llength $bootsupport_modules]} {
puts stderr "bootsupport/modules$which - No local bootsupport modules configured for updating"
} else {
if {[catch { if {[catch {
#---------- #----------
set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck] set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck]
$boot_installer set_source_target $projectroot $projectroot/src/bootsupport $boot_installer set_source_target $projectroot $projectroot/src/bootsupport
set boot_event [$boot_installer start_event {-make_step bootsupport}] set boot_event [$boot_installer start_event {-make_step bootsupport}]
#---------- #----------
} errM]} { } errM]} {
puts stderr "Unable to use punkcheck for bootsupport error: $errM" puts stderr "Unable to use punkcheck for bootsupport error: $errM"
set boot_event "" set boot_event ""
} }
foreach {relpath modulematch} $bootsupport_modules { foreach {relpath modulematch} $bootsupport_modules {
set modulematch [string trim $modulematch :] set modulematch [string trim $modulematch :]
set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]] set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]]
set srclocation [file join $projectroot $relpath $module_subpath] set srclocation [file join $projectroot $relpath $module_subpath]
#puts stdout "$relpath $modulematch $module_subpath $srclocation" #puts stdout "$relpath $modulematch $module_subpath $srclocation"
if {[string first - $modulematch]} { if {[string first - $modulematch]} {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm] set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm]
} else { } else {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm] set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm]
} }
if {![llength $pkgmatches]} { if {![llength $pkgmatches]} {
puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation" puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation"
continue continue
} }
set modulematch_is_glob [regexp {[*?\[\]]} $modulematch] set modulematch_is_glob [regexp {[*?\[\]]} $modulematch]
if {!$modulematch_is_glob} { if {!$modulematch_is_glob} {
#if modulematch was specified without globs - only copy latest #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 #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 pkgmatches [lsort -command modfile_sort $pkgmatches]
set latestfile [lindex $pkgmatches end] set latestfile [lindex $pkgmatches end]
#set latestver [lindex [split [file rootname $latestfile] -] 1] #set latestver [lindex [split [file rootname $latestfile] -] 1]
set copy_files $latestfile set copy_files $latestfile
} else { } else {
#globs in modulematch - may be different packages matched by glob - copy all versions of matches #globs in modulematch - may be different packages matched by glob - copy all versions of matches
#review #review
set copy_files $pkgmatches set copy_files $pkgmatches
} }
foreach cfile $copy_files { foreach cfile $copy_files {
set srcfile [file join $srclocation $cfile] set srcfile [file join $srclocation $cfile]
set tgtfile [file join $targetroot $module_subpath $cfile] set tgtfile [file join $targetroot $module_subpath $cfile]
if {$boot_event ne ""} { if {$boot_event ne ""} {
#---------- #----------
$boot_event targetset_init INSTALL $tgtfile $boot_event targetset_init INSTALL $tgtfile
$boot_event targetset_addsource $srcfile $boot_event targetset_addsource $srcfile
#---------- #----------
if {\ if {\
[llength [dict get [$boot_event targetset_source_changes] changed]]\ [llength [dict get [$boot_event targetset_source_changes] changed]]\
|| [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\ || [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\
} { } {
file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists
$boot_event targetset_started $boot_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile"
if {[catch { if {[catch {
file copy -force $srcfile $tgtfile file copy -force $srcfile $tgtfile
} errM]} { } errM]} {
$boot_event targetset_end FAILED $boot_event targetset_end FAILED
} else {
$boot_event targetset_end OK
}
# -- --- --- --- --- ---
} else { } else {
puts -nonewline stderr "." $boot_event targetset_end OK
$boot_event targetset_end SKIPPED
} }
$boot_event end # -- --- --- --- --- ---
} else { } else {
file copy -force $srcfile $tgtfile puts -nonewline stderr "."
$boot_event targetset_end SKIPPED
} }
$boot_event end
} else {
file copy -force $srcfile $tgtfile
} }
} }
if {$boot_event ne ""} {
puts \n
$boot_event destroy
$boot_installer destroy
}
} }
if {$boot_event ne ""} {
puts \n
$boot_event destroy
$boot_installer destroy
}
} }
} }
} }
} }
@ -1699,59 +1699,53 @@ if {$::punkboot::command ni {project modules vfs}} {
#install src vendor contents (from version controlled src folder) to base of project (same target folders as our own src/modules etc ie to paths that go on the auto_path and in tcl::tm::list) #install src vendor contents (from version controlled src folder) to base of project (same target folders as our own src/modules etc ie to paths that go on the auto_path and in tcl::tm::list)
if {$::punkboot::command in {project modules}} { if {$::punkboot::command in {project modules}} {
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
lappend vendorlibfolders vendorlib
foreach lf $vendorlibfolders {
if {[file exists $sourcefolder/$lf]} {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
set which ""
}
set target_lib_folder $projectroot/lib$which
file mkdir $projectroot/lib$which
#exclude README.md from source folder - but only the root one
#-antiglob_paths takes relative patterns e.g
# */test.txt will only match test.txt exactly one level deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# **/test.txt will match at any level below the root (but not in the root)
set antipaths [list\
README.md\
]
puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
}
if {![llength $vendorlibfolders]} {
puts stderr "VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found."
}
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders { foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} { lassign [split $vf _] _vm tclx
lassign [split $vf _] _vm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
} else { set which ""
set which ""
}
set target_module_folder $projectroot/modules$which
file mkdir $target_module_folder
#install .tm *and other files*
puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} }
set target_module_folder $projectroot/modules$which
file mkdir $target_module_folder
#install .tm *and other files*
puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} }
if {![llength $vendormodulefolders]} { if {![llength $vendormodulefolders]} {
puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found." puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found."
} }
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib vendorlib_tcl*]
foreach lf $vendorlibfolders {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
set which ""
}
set target_lib_folder $projectroot/lib$which
file mkdir $projectroot/lib$which
#exclude README.md from source folder - but only the root one
#-antiglob_paths takes relative patterns e.g
# */test.txt will only match test.txt exactly one level deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# **/test.txt will match at any level below the root (but not in the root)
set antipaths [list\
README.md\
]
puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
if {![llength $vendorlibfolders]} {
puts stderr "VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found."
}
######################################################## ########################################################
#templates #templates
#e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync #e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync
@ -1760,10 +1754,10 @@ if {$::punkboot::command in {project modules}} {
set old_layout_update_list [list\ set old_layout_update_list [list\
[list project $sourcefolder/modules/punk/mix/templates]\ [list project $sourcefolder/modules/punk/mix/templates]\
[list basic $sourcefolder/mixtemplates]\ [list basic $sourcefolder/mixtemplates]\
] ]
set layout_bases [list\ set layout_bases [list\
$sourcefolder/project_layouts/custom/_project\ $sourcefolder/project_layouts/custom/_project\
] ]
foreach layoutbase $layout_bases { foreach layoutbase $layout_bases {
if {![file exists $layoutbase]} { if {![file exists $layoutbase]} {
@ -1823,27 +1817,25 @@ if {$::punkboot::command in {project modules}} {
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*] set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib lappend projectlibfolders lib
foreach lf $projectlibfolders { foreach lf $projectlibfolders {
if {[file exists $sourcefolder/$lf]} { lassign [split $lf _] _vm tclx
lassign [split $lf _] _vm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
} else { set which ""
set which "" }
} set target_lib_folder $projectroot/lib$which
set target_lib_folder $projectroot/lib$which file mkdir $projectroot/lib$which
file mkdir $projectroot/lib$which #exclude README.md from source folder - but only the root one
#exclude README.md from source folder - but only the root one #-antiglob_paths takes relative patterns e.g
#-antiglob_paths takes relative patterns e.g # */test.txt will only match test.txt exactly one level deep.
# */test.txt will only match test.txt exactly one level deep. # */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep. # **/test.txt will match at any level below the root (but not in the root)
# **/test.txt will match at any level below the root (but not in the root) set antipaths [list\
set antipaths [list\ README.md\
README.md\ ]
] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] puts stdout [punkcheck::summarize_install_resultdict $resultdict]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
} }
if {![llength $projectlibfolders]} { if {![llength $projectlibfolders]} {
puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found." puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found."
@ -2355,7 +2347,7 @@ foreach vfstail $vfs_tails {
} else { } else {
lappend runtimes $matchrt lappend runtimes $matchrt
} }
} }
} }
#assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config #assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config

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

File diff suppressed because it is too large Load Diff

21
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm

@ -20,7 +20,7 @@
#[manpage_begin fauxlink_module_fauxlink 0 0.1.1] #[manpage_begin fauxlink_module_fauxlink 0 0.1.1]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}] #[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}]
#[moddesc {fauxlink .fxlnk}] [comment {-- Description at end of page heading --}] #[moddesc {.fauxlink .fxlnk}] [comment {-- Description at end of page heading --}]
#[require fauxlink] #[require fauxlink]
#[keywords symlink faux fake shortcut toml] #[keywords symlink faux fake shortcut toml]
#[description] #[description]
@ -29,18 +29,19 @@
#[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as #[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as
#[para] archiving and packaging systems. #[para] archiving and packaging systems.
#[para] The target is specified in a minimally-encoded form in the filename itself - but still human readable. #[para] The target is specified in a minimally-encoded form in the filename itself - but still human readable.
#[para] format of name <nominalname>#<encodedtarget>.fxlnk #[para] format of name <nominalname>#<encodedtarget>.fauxlink
#[para] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget> #[para] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget>
#[para] The file extension must be .fauxlink or .fxlnk
#[para] The + symbol substitutes for forward-slashes. #[para] The + symbol substitutes for forward-slashes.
#[para] Other chars can be encoded using url-like encoding - (but only up to %7E !) #[para] Other chars can be encoded using url-like encoding - (but only up to %7E !)
#[para] We deliberately treat higher % sequences literally. #[para] We deliberately treat higher % sequences literally.
#[para] This means actual uri::urn encoded unicode sequences (e.g %E2%99%A5 [lb]heart[rb]) can remain literal for linking to urls. #[para] This means actual uri::urn encoded unicode sequences (e.g %E2%99%A5 [lb]heart[rb]) can remain literal for linking to urls.
#[para] e.g if an actual + or # is required in a filename or path segment they can be encoded as %2B & %23 #[para] e.g if an actual + or # is required in a filename or path segment they can be encoded as %2B & %23
#[para] e.g a link to a file file#A.txt in parent dir could be: #[para] e.g a link to a file file#A.txt in parent dir could be:
#[para] file%23A.txt#..+file%23A.txt.fxlnk #[para] file%23A.txt#..+file%23A.txt.fauxlink
#[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fxlnk #[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fauxlink
#[para] The <nominalname> can be unrelated to the actual target #[para] The <nominalname> can be unrelated to the actual target
#[para] e.g datafile.dat#..+file%23A.txt.fxlnk #[para] e.g datafile.dat#..+file%23A.txt.fauxlink
#[para] This system has no filesystem support - and must be completely application driven. #[para] This system has no filesystem support - and must be completely application driven.
#[para] This can be useful for example in application test packages which may be tarred or zipped and moved cross platform. #[para] This can be useful for example in application test packages which may be tarred or zipped and moved cross platform.
#[para] The target being fully specified in the name means the file doesn't have to be read for the target to be determined #[para] The target being fully specified in the name means the file doesn't have to be read for the target to be determined
@ -63,9 +64,9 @@
#https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/url-encoding-unc-paths-not-url-decoded #https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/url-encoding-unc-paths-not-url-decoded
# ie "//server/c/Program files" works but "//server/c/Program%20Files" is now treated by windows as a literal path with %20 in it. # ie "//server/c/Program files" works but "//server/c/Program%20Files" is now treated by windows as a literal path with %20 in it.
#Using fauxlink - a link would be: #Using fauxlink - a link would be:
# "my-program-files#++server+c+Program%20Files.fxlnk" # "my-program-files#++server+c+Program%20Files.fauxlink"
#If we needed the old-style literal %20 it would become #If we needed the old-style literal %20 it would become
# "my-program-files#++server+c+Program%2520Files.fxlnk" # "my-program-files#++server+c+Program%2520Files.fauxlink"
# #
# The file:// scheme on windows supposedly *does* decode %xx (for use in a browser) # The file:// scheme on windows supposedly *does* decode %xx (for use in a browser)
# e.g # e.g
@ -296,12 +297,12 @@ namespace eval fauxlink {
set is_fauxlink 0 set is_fauxlink 0
#we'll process anyway - but return the result wrapped #we'll process anyway - but return the result wrapped
#This should allow deliberate erroring for the calling dict user if the extension difference is inadvertent #This should allow deliberate erroring for the calling dict user if the extension difference is inadvertent
#(e.g blindly processing all files in a folder that is normally only .fxlnk files - but then something added that happens #(e.g blindly processing all files in a folder that is normally only .fauxlink files - but then something added that happens
# to have # characters in it) # to have # characters in it)
#It also means if someone really wants to use the fauxlink semantics on a different file type #It also means if someone really wants to use the fauxlink semantics on a different file type
# - they can - but just have to access the results differently and take that (minor) risk. # - they can - but just have to access the results differently and take that (minor) risk.
#error "fauxlink::resolve refusing to process link $link - file extension must be .fxlnk or .fauxlink" #error "fauxlink::resolve refusing to process link $link - file extension must be .fxlnk or .fauxlink"
set err_extra "\nnonstandard extension '$extension_name' for fauxlink. Check that the call to fauxlink::resolve was deliberate" set err_extra "\nnonstandard extension '$extension_name' for fauxlink. (expected .fxlnk or .fauxlink) Check that the call to fauxlink::resolve was deliberate"
} else { } else {
set is_fauxlink 1 set is_fauxlink 1
set err_extra "" set err_extra ""
@ -318,7 +319,7 @@ namespace eval fauxlink {
#if there are 4 parts - the 3rd part is a tagset where each tag begins with @ #if there are 4 parts - the 3rd part is a tagset where each tag begins with @
#and each subsequent part is a comment. Empty comments are stripped from the comments list #and each subsequent part is a comment. Empty comments are stripped from the comments list
#A tagset can be empty - but if it's not empty it must contain at least one @ and must start with @ #A tagset can be empty - but if it's not empty it must contain at least one @ and must start with @
#e.g name.txt#path#@tag1@tag2#test###.fxlnk #e.g name.txt#path#@tag1@tag2#test###.fauxlink
#has a name, a target, 2 tags and one comment #has a name, a target, 2 tags and one comment
#check namespec already has required chars encoded #check namespec already has required chars encoded

12822
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/metaface-1.2.5.tm

File diff suppressed because it is too large Load Diff

705
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/modpod-0.1.0.tm

@ -1,705 +0,0 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix 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.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application modpod 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin modpod_module_modpod 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 modpod]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of modpod
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by modpod
#[list_begin itemized]
package require Tcl 8.6-
package require struct::set ;#review
package require punk::lib
package require punk::args
#*** !doctools
#[item] [package {Tcl 8.6-}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval modpod::class {
#*** !doctools
#[subsection {Namespace modpod::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 modpod {
namespace export {[a-z]*}; # Convention: export all lowercase
variable connected
if {![info exists connected(to)]} {
set connected(to) list
}
variable modpodscript
set modpodscript [info script]
if {[string tolower [file extension $modpodscript]] eq ".tcl"} {
set connected(self) [file dirname $modpodscript]
} else {
#expecting a .tm
set connected(self) $modpodscript
}
variable loadables [info sharedlibextension]
variable sourceables {.tcl .tk} ;# .tm ?
#*** !doctools
#[subsection {Namespace modpod}]
#[para] Core API functions for modpod
#[list_begin definitions]
#proc sample1 {p1 args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [opt {?option value...?}]]
# #[para]Description of sample1
# return "ok"
#}
proc connect {args} {
puts stderr "modpod::connect--->>$args"
set argd [punk::args::get_dict {
-type -default ""
*values -min 1 -max 1
path -type string -minlen 1 -help "path to .tm file or toplevel .tcl script within #modpod-<pkg>-<ver> folder (unwrapped modpod)"
} $args]
catch {
punk::lib::showdict $argd ;#heavy dependencies
}
set opt_path [dict get $argd values path]
variable connected
set original_connectpath $opt_path
set modpodpath [modpod::system::normalize $opt_path] ;#
if {$modpodpath in $connected(to)} {
return [dict create ok ALREADY_CONNECTED]
}
lappend connected(to) $modpodpath
set connected(connectpath,$opt_path) $original_connectpath
set is_sourced [expr {[file normalize $modpodpath] eq [file normalize [info_script]]}]
set connected(location,$modpodpath) [file dirname $modpodpath]
set connected(startdata,$modpodpath) -1
set connected(type,$modpodpath) [dict get $argd-opts -type]
set connected(fh,$modpodpath) ""
if {[string range [file tail $modpodpath] 0 7] eq "#modpod-"} {
set connected(type,$modpodpath) "unwrapped"
lassign [::split [file tail [file dirname $modpodpath]] -] connected(package,$modpodpath) connected(version,$modpodpath)
set this_pkg_tm_folder [file dirname [file dirname $modpodpath]]
} else {
#connect to .tm but may still be unwrapped version available
lassign [::split [file rootname [file tail $modpodath]] -] connected(package,$modpodpath) connected(version,$modpodpath)
set this_pkg_tm_folder [file dirname $modpodpath]
if {$connected(type,$modpodpath) ne "unwrapped"} {
#Not directly connected to unwrapped version - but may still be redirected there
set unwrappedFolder [file join $connected(location,$modpodpath) #modpod-$connected(package,$modpodpath)-$connected(version,$modpodpath)]
if {[file exists $unwrappedFolder]} {
#folder with exact version-match must exist for redirect to 'unwrapped'
set con(type,$modpodpath) "modpod-redirecting"
}
}
}
set unwrapped_tm_file [file join $this_pkg_tm_folder] "[set connected(package,$modpodpath)]-[set connected(version,$modpodpath)].tm"
set connected(tmfile,$modpodpath)
set tail_segments [list]
set lcase_tmfile_segments [string tolower [file split $this_pkg_tm_folder]]
set lcase_modulepaths [string tolower [tcl::tm::list]]
foreach lc_mpath $lcase_modulepaths {
set mpath_segments [file split $lc_mpath]
if {[llength [struct::set intersect $lcase_tmfile_segments $mpath_segments]] == [llength $mpath_segments]} {
set tail_segments [lrange [file split $this_pkg_tm_folder] [llength $mpath_segments] end]
break
}
}
if {[llength $tail_segments]} {
set connected(fullpackage,$modpodpath) [join [concat $tail_segments [set connected(package,$modpodpath)]] ::] ;#full name of package as used in package require
} else {
set connected(fullpackage,$modpodpath) [set connected(package,$modpodpath)]
}
switch -exact -- $connected(type,$modpodpath) {
"modpod-redirecting" {
#redirect to the unwrapped version
set loadscript_name [file join $unwrappedFolder #modpod-loadscript-$con(package,$modpod).tcl]
}
"unwrapped" {
if {[info commands ::thread::id] ne ""} {
set from [pid],[thread::id]
} else {
set from [pid]
}
#::modpod::Puts stderr "$from-> Package $connected(package,$modpodpath)-$connected(version,$modpodpath) is using unwrapped version: $modpodpath"
return [list ok ""]
}
default {
#autodetect .tm - zip/tar ?
#todo - use vfs ?
#connect to tarball - start at 1st header
set connected(startdata,$modpodpath) 0
set fh [open $modpodpath r]
set connected(fh,$modpodpath) $fh
fconfigure $fh -encoding iso8859-1 -translation binary -eofchar {}
if {$connected(startdata,$modpodpath) >= 0} {
#verify we have a valid tar header
if {![catch {::modpod::system::tar::readHeader [red $fh 512]}]} {
seek $fh $connected(startdata,$modpodpath) start
return [list ok $fh]
} else {
#error "cannot verify tar header"
}
}
lpop connected(to) end
set connected(startdata,$modpodpath) -1
unset connected(fh,$modpodpath)
catch {close $fh}
return [dict create err {Does not appear to be a valid modpod}]
}
}
}
proc disconnect {{modpod ""}} {
variable connected
if {![llength $connected(to)]} {
return 0
}
if {$modpod eq ""} {
puts stderr "modpod::disconnect WARNING: modpod not explicitly specified. Disconnecting last connected: [lindex $connected(to) end]"
set modpod [lindex $connected(to) end]
}
if {[set posn [lsearch $connected(to) $modpod]] == -1} {
puts stderr "modpod::disconnect WARNING: disconnect called when not connected: $modpod"
return 0
}
if {[string length $connected(fh,$modpod)]} {
close $connected(fh,$modpod)
}
array unset connected *,$modpod
set connected(to) [lreplace $connected(to) $posn $posn]
return 1
}
proc get {args} {
set argd [punk::args::get_dict {
-from -default "" -help "path to pod"
*values -min 1 -max 1
filename
} $args]
set frompod [dict get $argd opts -from]
set filename [dict get $argd values filename]
variable connected
set modpod [::tarjar::system::connect_if_not $frompod]
set fh $connected(fh,$modpod)
if {$connected(type,$modpod) eq "unwrapped"} {
#for unwrapped connection - $connected(location) already points to the #modpod-pkg-ver folder
if {[string range $filename 0 0 eq "/"]} {
#absolute path (?)
set path [file join $connected(location,$modpod) .. [string trim $filename /]]
} else {
#relative path - use #modpod-xxx as base
set path [file join $connected(location,$modpod) $filename]
}
set fd [open $path r]
#utf-8?
#fconfigure $fd -encoding iso8859-1 -translation binary
return [list ok [lindex [list [read $fd] [close $fd]] 0]]
} else {
#read from vfs
puts stderr "get $filename from wrapped pod '$frompod' not implemented"
}
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace modpod ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval modpod::lib {
namespace export {[a-z]*}; # Convention: export all lowercase
namespace path [namespace parent]
#*** !doctools
#[subsection {Namespace modpod::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
proc is_valid_tm_version {versionpart} {
#Needs to be suitable for use with Tcl's 'package vcompare'
if {![catch [list package vcompare $versionparts $versionparts]]} {
return 1
} else {
return 0
}
}
proc make_zip_modpod {zipfile outfile} {
set mount_stub {
#zip file with Tcl loader prepended.
#generated using modpod::make_zip_modpod
if {[catch {file normalize [info script]} modfile]} {
error "modpod zip stub error. Unable to determine module path. (possible safe interp restrictions?)"
}
if {$modfile eq "" || ![file exists $modfile]} {
error "modpod zip stub error. Unable to determine module path"
}
set moddir [file dirname $modfile]
set mod_and_ver [file rootname [file tail $modfile]]
lassign [split $mod_and_ver -] moduletail version
if {[file exists $moddir/#modpod-$mod_and_ver]} {
source $moddir/#modpod-$mod_and_ver/$mod_and_ver.tm
} else {
#determine module namespace so we can mount appropriately
proc intersect {A B} {
if {[llength $A] == 0} {return {}}
if {[llength $B] == 0} {return {}}
if {[llength $B] > [llength $A]} {
set res $A
set A $B
set B $res
}
set res {}
foreach x $A {set ($x) {}}
foreach x $B {
if {[info exists ($x)]} {
lappend res $x
}
}
return $res
}
set lcase_tmfile_segments [string tolower [file split $moddir]]
set lcase_modulepaths [string tolower [tcl::tm::list]]
foreach lc_mpath $lcase_modulepaths {
set mpath_segments [file split $lc_mpath]
if {[llength [intersect $lcase_tmfile_segments $mpath_segments]] == [llength $mpath_segments]} {
set tail_segments [lrange [file split $moddir] [llength $mpath_segments] end] ;#use propertly cased tail
break
}
}
if {[llength $tail_segments]} {
set fullpackage [join [concat $tail_segments $moduletail] ::] ;#full name of package as used in package require
set mount_at #modpod/[file join {*}$tail_segments]/#mounted-modpod-$mod_and_ver
} else {
set fullpackage $moduletail
set mount_at #modpod/#mounted-modpod-$mod_and_ver
}
if {[info commands tcl::zipfs::mount] ne ""} {
#argument order changed to be consistent with vfs::zip::Mount etc
#early versions: zipfs::Mount mountpoint zipname
#since 2023-09: zipfs::Mount zipname mountpoint
#don't use 'file exists' when testing mountpoints. (some versions at least give massive delays on windows platform for non-existance)
#This is presumably related to // being interpreted as a network path
set mountpoints [dict keys [tcl::zipfs::mount]]
if {"//zipfs:/$mount_at" ni $mountpoints} {
#despite API change tcl::zipfs package version was unfortunately not updated - so we don't know argument order without trying it
if {[catch {
#tcl::zipfs::mount $modfile //zipfs:/#mounted-modpod-$mod_and_ver ;#extremely slow if this is a wrong guess (artifact of aforementioned file exists issue ?)
#puts "tcl::zipfs::mount $modfile $mount_at"
tcl::zipfs::mount $modfile $mount_at
} errM]} {
#try old api
if {![catch {tcl::zipfs::mount //zipfs:/$mount_at $modfile}]} {
puts stderr "modpod stub>>> tcl::zipfs::mount <file> <mountpoint> failed.\nbut old api: tcl::zipfs::mount <mountpoint> <file> succeeded\n tcl::zipfs::mount //zipfs://$mount_at $modfile"
puts stderr "Consider upgrading tcl runtime to one with fixed zipfs API"
}
}
if {![file exists //zipfs:/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm]} {
puts stderr "modpod stub>>> mount at //zipfs:/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm failed\n zipfs mounts: [zipfs mount]"
#tcl::zipfs::unmount //zipfs:/$mount_at
error "Unable to find $mod_and_ver.tm in $modfile for module $fullpackage"
}
}
# #modpod-$mod_and_ver subdirectory always present in the archive so it can be conveniently extracted and run in that form
source //zipfs:/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm
} else {
#fallback to slower vfs::zip
#NB. We don't create the intermediate dirs - but the mount still works
if {![file exists $moddir/$mount_at]} {
if {[catch {package require vfs::zip} errM]} {
set msg "Unable to load vfs::zip package to mount module $mod_and_ver"
append msg \n "If vfs::zip is unavailable - the module can still be loaded by manually unzipping the file $modfile in place."
append msg \n "The unzipped data will all be contained in a folder named #modpod-$mod_and_ver in the same parent folder as $modfile"
error $msg
} else {
set fd [vfs::zip::Mount $modfile $moddir/$mount_at]
if {![file exists $moddir/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm]} {
vfs::zip::Unmount $fd $moddir/$mount_at
error "Unable to find $mod_and_ver.tm in $modfile for module $fullpackage"
}
}
}
source $moddir/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm
}
}
#zipped data follows
}
#todo - test if zipfile has #modpod-loadcript.tcl before even creating
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub
}
proc make_zip_modpod1 {zipfile outfile} {
set mount_stub {
#zip file with Tcl loader prepended.
#generated using modpod::make_zip_modpod
if {[catch {file normalize [info script]} modfile]} {
error "modpod zip stub error. Unable to determine module path. (possible safe interp restrictions?)"
}
if {$modfile eq "" || ![file exists $modfile]} {
error "modpod zip stub error. Unable to determine module path"
}
set moddir [file dirname $modfile]
set mod_and_ver [file rootname [file tail $modfile]]
lassign [split $mod_and_ver -] moduletail version
if {[file exists $moddir/#modpod-$mod_and_ver]} {
source $moddir/#modpod-$mod_and_ver/$mod_and_ver.tm
} else {
if {![file exists $moddir/#mounted-modpod-$mod_and_ver]} {
if {[catch {package require vfs::zip} errM]} {
set msg "Unable to load vfs::zip package to mount module $mod_and_ver"
append msg \n "If vfs::zip is unavailable - the module can still be loaded by manually unzipping the file $modfile in place."
append msg \n "The unzipped data will all be contained in a folder named #modpod-$mod_and_ver in the same parent folder as $
}
set fd [vfs::zip::Mount $modfile $moddir/#mounted-modpod-$mod_and_ver]
if {![file exists $moddir/#mounted-modpod-$mod_and_ver/#modpod-$mod_and_ver/$mod_and_ver.tm]} {
vfs::zip::Unmount $fd $moddir/#mounted-modpod-$mod_and_ver
error "Unable to find #modpod-$mod_and_ver/$mod_and_ver.tm in $modfile"
}
}
source $moddir/#mounted-modpod-$mod_and_ver/#modpod-$mod_and_ver/$mod_and_ver.tm
}
#zipped data follows
}
#todo - test if zipfile has #modpod-loadcript.tcl before even creating
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub
}
proc make_zip_source_mountable {zipfile outfile} {
set mount_stub {
package require vfs::zip
vfs::zip::Mount [info script] [info script]
}
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace modpod::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
namespace eval modpod::system {
#*** !doctools
#[subsection {Namespace modpod::system}]
#[para] Internal functions that are not part of the API
#deflate,store only supported
proc make_mountable_zip {zipfile outfile mount_stub} {
set in [open $zipfile r]
fconfigure $in -encoding iso8859-1 -translation binary
set out [open $outfile w+]
fconfigure $out -encoding iso8859-1 -translation binary
puts -nonewline $out $mount_stub
set offset [tell $out]
lappend report "sfx stub size: $offset"
fcopy $in $out
close $in
set size [tell $out]
#Now seek in $out to find the end of directory signature:
#The structure itself is 24 bytes Long, followed by a maximum of 64Kbytes text
if {$size < 65559} {
set seek 0
} else {
set seek [expr {$size - 65559}]
}
seek $out $seek
set data [read $out]
set start_of_end [string last "\x50\x4b\x05\x06" $data]
#set start_of_end [expr {$start_of_end + $seek}]
incr start_of_end $seek
lappend report "START-OF-END: $start_of_end ([expr {$start_of_end - $size}]) [string length $data]"
seek $out $start_of_end
set end_of_ctrl_dir [read $out]
binary scan $end_of_ctrl_dir issssiis eocd(signature) eocd(disknbr) eocd(ctrldirdisk) \
eocd(numondisk) eocd(totalnum) eocd(dirsize) eocd(diroffset) eocd(comment_len)
lappend report "End of central directory: [array get eocd]"
seek $out [expr {$start_of_end+16}]
#adjust offset of start of central directory by the length of our sfx stub
puts -nonewline $out [binary format i [expr {$eocd(diroffset) + $offset}]]
flush $out
seek $out $start_of_end
set end_of_ctrl_dir [read $out]
binary scan $end_of_ctrl_dir issssiis eocd(signature) eocd(disknbr) eocd(ctrldirdisk) \
eocd(numondisk) eocd(totalnum) eocd(dirsize) eocd(diroffset) eocd(comment_len)
# 0x06054b50 - end of central dir signature
puts stderr "$end_of_ctrl_dir"
puts stderr "comment_len: $eocd(comment_len)"
puts stderr "eocd sig: $eocd(signature) [punk::lib::dec2hex $eocd(signature)]"
lappend report "New dir offset: $eocd(diroffset)"
lappend report "Adjusting $eocd(totalnum) zip file items."
catch {
punk::lib::showdict -roottype list -chan stderr $report ;#heavy dependencies
}
seek $out $eocd(diroffset)
for {set i 0} {$i <$eocd(totalnum)} {incr i} {
set current_file [tell $out]
set fileheader [read $out 46]
puts --------------
puts [ansistring VIEW -lf 1 $fileheader]
puts --------------
#binary scan $fileheader is2sss2ii2s3ssii x(sig) x(version) x(flags) x(method) \
# x(date) x(crc32) x(sizes) x(lengths) x(diskno) x(iattr) x(eattr) x(offset)
binary scan $fileheader ic4sss2ii2s3ssii x(sig) x(version) x(flags) x(method) \
x(date) x(crc32) x(sizes) x(lengths) x(diskno) x(iattr) x(eattr) x(offset)
set ::last_header $fileheader
puts "sig: $x(sig) (hex: [punk::lib::dec2hex $x(sig)])"
puts "ver: $x(version)"
puts "method: $x(method)"
#33639248 dec = 0x02014b50 - central file header signature
if { $x(sig) != 33639248 } {
error "modpod::system::make_mountable_zip Bad file header signature at item $i: dec:$x(sig) hex:[punk::lib::dec2hex $x(sig)]"
}
foreach size $x(lengths) var {filename extrafield comment} {
if { $size > 0 } {
set x($var) [read $out $size]
} else {
set x($var) ""
}
}
set next_file [tell $out]
lappend report "file $i: $x(offset) $x(sizes) $x(filename)"
seek $out [expr {$current_file+42}]
puts -nonewline $out [binary format i [expr {$x(offset)+$offset}]]
#verify:
flush $out
seek $out $current_file
set fileheader [read $out 46]
lappend report "old $x(offset) + $offset"
binary scan $fileheader is2sss2ii2s3ssii x(sig) x(version) x(flags) x(method) \
x(date) x(crc32) x(sizes) x(lengths) x(diskno) x(iattr) x(eattr) x(offset)
lappend report "new $x(offset)"
seek $out $next_file
}
close $out
#pdict/showdict reuire punk & textlib - ie lots of dependencies
#don't fall over just because of that
catch {
punk::lib::showdict -roottype list -chan stderr $report
}
#puts [join $report \n]
return
}
proc connect_if_not {{podpath ""}} {
upvar ::modpod::connected connected
set podpath [::modpod::system::normalize $podpath]
set docon 0
if {![llength $connected(to)]} {
if {![string length $podpath]} {
error "modpod::system::connect_if_not - Not connected to a modpod file, and no podpath specified"
} else {
set docon 1
}
} else {
if {![string length $podpath]} {
set podpath [lindex $connected(to) end]
puts stderr "modpod::system::connect_if_not WARNING: using last connected modpod:$podpath for operation\n -podpath not explicitly specified during operation: [info level -1]"
} else {
if {$podpath ni $connected(to)} {
set docon 1
}
}
}
if {$docon} {
if {[lindex [modpod::connect $podpath]] 0] ne "ok"} {
error "modpod::system::connect_if_not error. file $podpath does not seem to be a valid modpod"
} else {
return $podpath
}
}
#we were already connected
return $podpath
}
proc myversion {} {
upvar ::modpod::connected connected
set script [info script]
if {![string length $script]} {
error "No result from \[info script\] - modpod::system::myversion should only be called from within a loading modpod"
}
set fname [file tail [file rootname [file normalize $script]]]
set scriptdir [file dirname $script]
if {![string match "#modpod-*" $fname]} {
lassign [lrange [split $fname -] end-1 end] _pkgname version
} else {
lassign [scan [file tail [file rootname $script]] {#modpod-loadscript-%[a-z]-%s}] _pkgname version
if {![string length $version]} {
#try again on the name of the containing folder
lassign [scan [file tail $scriptdir] {#modpod-%[a-z]-%s}] _pkgname version
#todo - proper walk up the directory tree
if {![string length $version]} {
#try again on the grandparent folder (this is a standard depth for sourced .tcl files in a modpod)
lassign [scan [file tail [file dirname $scriptdir]] {#modpod-%[a-z]-%s}] _pkgname version
}
}
}
#tarjar::Log debug "'myversion' determined version for [info script]: $version"
return $version
}
proc myname {} {
upvar ::modpod::connected connected
set script [info script]
if {![string length $script]} {
error "No result from \[info script\] - modpod::system::myname should only be called from within a loading modpod"
}
return $connected(fullpackage,$script)
}
proc myfullname {} {
upvar ::modpod::connected connected
set script [info script]
#set script [::tarjar::normalize $script]
set script [file normalize $script]
if {![string length $script]} {
error "No result from \[info script\] - modpod::system::myfullname should only be called from within a loading tarjar"
}
return $::tarjar::connected(fullpackage,$script)
}
proc normalize {path} {
#newer versions of Tcl don't do tilde sub
#Tcl's 'file normalize' seems to do some unfortunate tilde substitution on windows.. (at least for relative paths)
# we take the assumption here that if Tcl's tilde substitution is required - it should be done before the path is provided to this function.
set matilda "<_tarjar_tilde_placeholder_>" ;#token that is *unlikely* to occur in the wild, and is somewhat self describing in case it somehow ..escapes..
set path [string map [list ~ $matilda] $path] ;#give our tildes to matilda to look after
set path [file normalize $path]
#set path [string tolower $path] ;#must do this after file normalize
return [string map [list $matilda ~] $path] ;#get our tildes back.
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide modpod [namespace eval modpod {
variable pkg modpod
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

1894
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/natsort-0.1.1.5.tm

File diff suppressed because it is too large Load Diff

1288
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm

File diff suppressed because it is too large Load Diff

1508
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm

File diff suppressed because it is too large Load Diff

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

@ -2469,7 +2469,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
if {$pretty} { if {$pretty} {
#return [pdict -channel none sgr_cache */%str,%ansiview] #return [pdict -channel none sgr_cache */%str,%ansiview]
return [pdict -channel none sgr_cache */%rpadstr-"sample",%ansiviewstyle] return [punk::lib::pdict -channel none sgr_cache */%rpadstr-"sample",%ansiviewstyle]
} }
if {[catch { if {[catch {
@ -5116,6 +5116,7 @@ tcl::namespace::eval punk::ansi::ta {
# arrow keys -> ESC O A, ESC O B, ESC O C, ESC O D # 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) # plus more for auxiliary keypad codes in keypad application mode (and some in numeric mode)
#regexp expanded syntax = ?x
variable re_ansi_detect {(?x) 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)))) (?:\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) |(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)

65
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm

@ -108,8 +108,10 @@ namespace eval punk::cap::handlers::templates {
#todo - handle wrapped or unwrapped tarjar files - in which case we have to adjust tmfolder appropriately #todo - handle wrapped or unwrapped tarjar files - in which case we have to adjust tmfolder appropriately
#set tpath [file normalize [file join $tmfile [dict get $capdict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder #set tpath [file normalize [file join $tmfile [dict get $capdict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder
set projectinfo [punk::repo::find_repos $tmfolder] #set projectinfo [punk::repo::find_repos $tmfolder] ;#slow - REVIEW
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $tmfolder]
#store the projectbase even if it's empty string #store the projectbase even if it's empty string
set extended_capdict $capdict set extended_capdict $capdict
set resolved_path [file join $tmfolder $path] set resolved_path [file join $tmfolder $path]
@ -148,8 +150,9 @@ namespace eval punk::cap::handlers::templates {
return 0 return 0
} }
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase] #set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
@ -166,8 +169,9 @@ namespace eval punk::cap::handlers::templates {
return 0 return 0
} }
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase] #set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
@ -183,8 +187,9 @@ namespace eval punk::cap::handlers::templates {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' which doesn't seem to exist" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' which doesn't seem to exist"
return 0 return 0
} }
set projectinfo [punk::repo::find_repos $normpath] #set projectinfo [punk::repo::find_repos $normpath]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $normpath]
#todo - verify no other provider has registered same absolute path - if sharing a project-external location is needed - they need their own subfolder #todo - verify no other provider has registered same absolute path - if sharing a project-external location is needed - they need their own subfolder
set extended_capdict $capdict set extended_capdict $capdict
@ -244,6 +249,18 @@ namespace eval punk::cap::handlers::templates {
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
namespace export * namespace export *
namespace eval class { namespace eval class {
variable PUNKARGS
#set argd [punk::args::get_dict {
# @id -id "::punk::cap::handlers::templates::class::api folders"
# -startdir -default ""
# @values -max 0
#} $args]
lappend PUNKARGS [list {
@id -id "::punk::cap::handlers::templates::class::api folders"
-startdir -default ""
@values -max 0
}]
oo::class create api { oo::class create api {
#return a dict keyed on folder with source pkg as value #return a dict keyed on folder with source pkg as value
constructor {capname} { constructor {capname} {
@ -253,11 +270,8 @@ namespace eval punk::cap::handlers::templates {
set capabilityname $capname set capabilityname $capname
} }
method folders {args} { method folders {args} {
set argd [punk::args::get_dict { #puts "--folders $args"
@id -id "::punk::cap::handlers::templates::class::api folders" set argd [punk::args::parse $args withid "::punk::cap::handlers::templates::class::api folders"]
-startdir -default ""
@values -max 0
} $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set opt_startdir [dict get $opts -startdir] set opt_startdir [dict get $opts -startdir]
@ -270,6 +284,10 @@ namespace eval punk::cap::handlers::templates {
set startdir $opt_startdir set startdir $opt_startdir
} }
} }
set searchbase $startdir
#set pathinfo [punk::repo::find_repos $searchbase] ;#relatively slow! REVIEW - pass as arg? cache?
#set pwd_projectroot [dict get $pathinfo closest]
set pwd_projectroot [punk::repo::find_project $searchbase]
variable capabilityname variable capabilityname
@ -314,9 +332,9 @@ namespace eval punk::cap::handlers::templates {
set module_projectroot [dict get $capdecl_extended projectbase] set module_projectroot [dict get $capdecl_extended projectbase]
dict lappend found_paths_module $vendor [list pkg $pkg path [dict get $capdecl_extended resolved_path] pathtype $pathtype projectbase $module_projectroot] dict lappend found_paths_module $vendor [list pkg $pkg path [dict get $capdecl_extended resolved_path] pathtype $pathtype projectbase $module_projectroot]
} elseif {$pathtype eq "currentproject_multivendor"} { } elseif {$pathtype eq "currentproject_multivendor"} {
set searchbase $startdir #set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase] #set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest] #set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} { if {$pwd_projectroot ne ""} {
set deckbase [file join $pwd_projectroot $path] set deckbase [file join $pwd_projectroot $path]
if {![file exists $deckbase]} { if {![file exists $deckbase]} {
@ -349,9 +367,9 @@ namespace eval punk::cap::handlers::templates {
} }
} }
} elseif {$pathtype eq "currentproject"} { } elseif {$pathtype eq "currentproject"} {
set searchbase $startdir #set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase] #set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest] #set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} { if {$pwd_projectroot ne ""} {
#path relative to projectroot already validated by handler as being within a currentproject_multivendor tree #path relative to projectroot already validated by handler as being within a currentproject_multivendor tree
set targetfolder [file join $pwd_projectroot $path] set targetfolder [file join $pwd_projectroot $path]
@ -489,8 +507,9 @@ namespace eval punk::cap::handlers::templates {
set refdict [my get_itemdict_projectlayoutrefs {*}$args] set refdict [my get_itemdict_projectlayoutrefs {*}$args]
set layoutdict [dict create] set layoutdict [dict create]
set projectinfo [punk::repo::find_repos $searchbase] #set projectinfo [punk::repo::find_repos $searchbase]
set projectroot [dict get $projectinfo closest] #set projectroot [dict get $projectinfo closest]
set projectroot [punk::repo::find_project $searchbase]
dict for {layoutname refinfo} $refdict { dict for {layoutname refinfo} $refdict {
set templatepathtype [dict get $refinfo sourceinfo pathtype] set templatepathtype [dict get $refinfo sourceinfo pathtype]
@ -760,6 +779,10 @@ namespace eval punk::cap::handlers::templates {
} }
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::cap::handlers::templates ::punk::cap::handlers::templates::class
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready

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

@ -1,487 +1,487 @@
tcl::namespace::eval punk::config { tcl::namespace::eval punk::config {
variable loaded variable loaded
variable startup ;#include env overrides variable startup ;#include env overrides
variable running variable running
variable punk_env_vars variable punk_env_vars
variable other_env_vars variable other_env_vars
variable vars variable vars
namespace export {[a-z]*} namespace export {[a-z]*}
#todo - XDG_DATA_HOME etc #todo - XDG_DATA_HOME etc
#https://specifications.freedesktop.org/basedir-spec/latest/ #https://specifications.freedesktop.org/basedir-spec/latest/
# see also: http://hiphish.github.io/blog/2020/08/30/dotfiles-were-a-mistake/ # see also: http://hiphish.github.io/blog/2020/08/30/dotfiles-were-a-mistake/
proc init {} { proc init {} {
variable defaults variable defaults
variable startup variable startup
variable running variable running
variable punk_env_vars variable punk_env_vars
variable punk_env_vars_config variable punk_env_vars_config
variable other_env_vars variable other_env_vars
variable other_env_vars_config variable other_env_vars_config
set exename "" set exename ""
catch { catch {
#catch for safe interps #catch for safe interps
#safe base will return empty string, ordinary safe interp will raise error #safe base will return empty string, ordinary safe interp will raise error
set exename [tcl::info::nameofexecutable] set exename [tcl::info::nameofexecutable]
} }
if {$exename ne ""} { if {$exename ne ""} {
set exefolder [file dirname $exename] set exefolder [file dirname $exename]
#default file logs to logs folder at same level as exe if writable, or empty string #default file logs to logs folder at same level as exe if writable, or empty string
set log_folder [file normalize $exefolder/../logs] ;#~2ms set log_folder [file normalize $exefolder/../logs] ;#~2ms
#tcl::dict::set startup scriptlib $exefolder/scriptlib #tcl::dict::set startup scriptlib $exefolder/scriptlib
#tcl::dict::set startup apps $exefolder/../../punkapps #tcl::dict::set startup apps $exefolder/../../punkapps
#todo - use punk main.tcl location instead - exefolder doesn't work if system tclsh used etc #todo - use punk main.tcl location instead - exefolder doesn't work if system tclsh used etc
set default_scriptlib $exefolder/scriptlib set default_scriptlib $exefolder/scriptlib
set default_apps $exefolder/../../punkapps set default_apps $exefolder/../../punkapps
if {[file isdirectory $log_folder] && [file writable $log_folder]} { if {[file isdirectory $log_folder] && [file writable $log_folder]} {
#tcl::dict::set startup logfile_stdout $log_folder/repl-exec-stdout.txt #tcl::dict::set startup logfile_stdout $log_folder/repl-exec-stdout.txt
#tcl::dict::set startup logfile_stderr $log_folder/repl-exec-stderr.txt #tcl::dict::set startup logfile_stderr $log_folder/repl-exec-stderr.txt
set default_logfile_stdout $log_folder/repl-exec-stdout.txt set default_logfile_stdout $log_folder/repl-exec-stdout.txt
set default_logfile_stderr $log_folder/repl-exec-stderr.txt set default_logfile_stderr $log_folder/repl-exec-stderr.txt
} else { } else {
set default_logfile_stdout "" set default_logfile_stdout ""
set default_logfile_stderr "" set default_logfile_stderr ""
} }
} else { } else {
#probably a safe interp - which cannot access info nameofexecutable even if access given to the location via punk::island #probably a safe interp - which cannot access info nameofexecutable even if access given to the location via punk::island
#review - todo? #review - todo?
#tcl::dict::set startup scriptlib "" #tcl::dict::set startup scriptlib ""
#tcl::dict::set startup apps "" #tcl::dict::set startup apps ""
set default_scriptlib "" set default_scriptlib ""
set default_apps "" set default_apps ""
set default_logfile_stdout "" set default_logfile_stdout ""
set default_logfile_stderr "" set default_logfile_stderr ""
} }
# auto_exec_mechanism ;#whether to use exec instead of experimental shellfilter::run # auto_exec_mechanism ;#whether to use exec instead of experimental shellfilter::run
#optional channel transforms on stdout/stderr. #optional channel transforms on stdout/stderr.
#can sometimes be useful to distinguish eventloop stdout/stderr writes compared to those triggered directly from repl commands #can sometimes be useful to distinguish eventloop stdout/stderr writes compared to those triggered directly from repl commands
#If no distinction necessary - should use default_color_<chan> #If no distinction necessary - should use default_color_<chan>
#The counterpart: default_color_<chan>_repl is a transform that is added and removed with each repl evaluation. #The counterpart: default_color_<chan>_repl is a transform that is added and removed with each repl evaluation.
#startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default #startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default
set default_color_stdout brightwhite ;#stdout colour including background calls (after etc) set default_color_stdout brightwhite ;#stdout colour including background calls (after etc)
set default_color_stdout_repl "" ;#stdout colour applied during direct repl call only set default_color_stdout_repl "" ;#stdout colour applied during direct repl call only
#This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful. #This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful.
#set default_color_stderr "red bold" #set default_color_stderr "red bold"
#set default_color_stderr "web-lightsalmon" #set default_color_stderr "web-lightsalmon"
set default_color_stderr yellow ;#limit to basic colours for wider terminal support. yellow = term-olive set default_color_stderr yellow ;#limit to basic colours for wider terminal support. yellow = term-olive
set default_color_stderr_repl "" ;#during repl call only set default_color_stderr_repl "" ;#during repl call only
set homedir "" set homedir ""
if {[catch { if {[catch {
#depending on which build of tcl - some safe interps prior to bugfix https://core.tcl-lang.org/tcl/info/3aa487993f will return a homedir value in an unmodified safe interp #depending on which build of tcl - some safe interps prior to bugfix https://core.tcl-lang.org/tcl/info/3aa487993f will return a homedir value in an unmodified safe interp
#other 'safe' interps may have explicitly made this available - we shouldn't override that decision here using interp issafe so we can't compensate for versions which shouldn't really be returning this in the safe interp #other 'safe' interps may have explicitly made this available - we shouldn't override that decision here using interp issafe so we can't compensate for versions which shouldn't really be returning this in the safe interp
set homedir [file home] set homedir [file home]
} errM]} { } errM]} {
#tcl 8.6 doesn't have file home.. try again #tcl 8.6 doesn't have file home.. try again
if {[info exists ::env(HOME)]} { if {[info exists ::env(HOME)]} {
set homedir $::env(HOME) set homedir $::env(HOME)
} }
} }
# per user xdg vars # per user xdg vars
# --- # ---
set default_xdg_config_home "" ;#config data - portable set default_xdg_config_home "" ;#config data - portable
set default_xdg_data_home "" ;#data the user likely to want to be portable set default_xdg_data_home "" ;#data the user likely to want to be portable
set default_xdg_cache_home "" ;#local cache set default_xdg_cache_home "" ;#local cache
set default_xdg_state_home "" ;#persistent user data such as logs, but not as important or as portable as those in xdg_data_home set default_xdg_state_home "" ;#persistent user data such as logs, but not as important or as portable as those in xdg_data_home
# --- # ---
set default_xdg_data_dirs "" ;#non-user specific set default_xdg_data_dirs "" ;#non-user specific
#xdg_config_dirs ? #xdg_config_dirs ?
#xdg_runtime_dir ? #xdg_runtime_dir ?
#review. we are assuming if we can't get a home dir - then all the xdg vars including xdg_data_dirs aren't likely to be useful (as presumably filesystem access is absent) #review. we are assuming if we can't get a home dir - then all the xdg vars including xdg_data_dirs aren't likely to be useful (as presumably filesystem access is absent)
#(safe interp generally won't have access to ::env either) #(safe interp generally won't have access to ::env either)
#This coupling doesn't necessarily hold - its possible the relevant env vars were copied to a safe interp - although that would be a policy that would make disabling 'info home' inconsistent. #This coupling doesn't necessarily hold - its possible the relevant env vars were copied to a safe interp - although that would be a policy that would make disabling 'info home' inconsistent.
if {$homedir ne ""} { if {$homedir ne ""} {
if {"windows" eq $::tcl_platform(platform)} { if {"windows" eq $::tcl_platform(platform)} {
#as much as I'd prefer to use ~/.local/share and ~/.config to keep them more consistent with unixlike platforms - the vast majority of apps put them where microsoft wants them. #as much as I'd prefer to use ~/.local/share and ~/.config to keep them more consistent with unixlike platforms - the vast majority of apps put them where microsoft wants them.
#we have a choice of LOCALAPPDATA vs APPDATA (local to machine vs potentially roaming/redirected in a corporate environment) #we have a choice of LOCALAPPDATA vs APPDATA (local to machine vs potentially roaming/redirected in a corporate environment)
#using the roaming location should not impact users who aren't using a domain controller but is potentially much more convenient for those who do. #using the roaming location should not impact users who aren't using a domain controller but is potentially much more convenient for those who do.
if {[info exists ::env(APPDATA)]} { if {[info exists ::env(APPDATA)]} {
set default_xdg_config_home $::env(APPDATA) set default_xdg_config_home $::env(APPDATA)
set default_xdg_data_home $::env(APPDATA) set default_xdg_data_home $::env(APPDATA)
} }
#The xdg_cache_home should be kept local #The xdg_cache_home should be kept local
if {[info exists ::env(LOCALAPPDATA)]} { if {[info exists ::env(LOCALAPPDATA)]} {
set default_xdg_cache_home $::env(LOCALAPPDATA) set default_xdg_cache_home $::env(LOCALAPPDATA)
set default_xdg_state_home $::env(LOCALAPPDATA) set default_xdg_state_home $::env(LOCALAPPDATA)
} }
if {[info exists ::env(PROGRAMDATA)]} { if {[info exists ::env(PROGRAMDATA)]} {
#- equiv env(ALLUSERSPROFILE) ? #- equiv env(ALLUSERSPROFILE) ?
set default_xdg_data_dirs $::env(PROGRAMDATA) set default_xdg_data_dirs $::env(PROGRAMDATA)
} }
} else { } else {
#follow defaults as specified on freedesktop.org e.g https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html #follow defaults as specified on freedesktop.org e.g https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
set default_xdg_config_home [file join $homedir .config] set default_xdg_config_home [file join $homedir .config]
set default_xdg_data_home [file join $homedir .local share] set default_xdg_data_home [file join $homedir .local share]
set default_xdg_cache_home [file join $homedir .cache] set default_xdg_cache_home [file join $homedir .cache]
set default_xdg_state_home [file join $homedir .local state] set default_xdg_state_home [file join $homedir .local state]
set default_xdg_data_dirs /usr/local/share set default_xdg_data_dirs /usr/local/share
} }
} }
set defaults [dict create\ set defaults [dict create\
apps $default_apps\ apps $default_apps\
config ""\ config ""\
configset ".punkshell"\ configset ".punkshell"\
scriptlib $default_scriptlib\ scriptlib $default_scriptlib\
color_stdout $default_color_stdout\ color_stdout $default_color_stdout\
color_stdout_repl $default_color_stdout_repl\ color_stdout_repl $default_color_stdout_repl\
color_stderr $default_color_stderr\ color_stderr $default_color_stderr\
color_stderr_repl $default_color_stderr_repl\ color_stderr_repl $default_color_stderr_repl\
logfile_stdout $default_logfile_stdout\ logfile_stdout $default_logfile_stdout\
logfile_stderr $default_logfile_stderr\ logfile_stderr $default_logfile_stderr\
logfile_active 0\ logfile_active 0\
syslog_stdout "127.0.0.1:514"\ syslog_stdout "127.0.0.1:514"\
syslog_stderr "127.0.0.1:514"\ syslog_stderr "127.0.0.1:514"\
syslog_active 0\ syslog_active 0\
auto_exec_mechanism exec\ auto_exec_mechanism exec\
auto_noexec 0\ auto_noexec 0\
xdg_config_home $default_xdg_config_home\ xdg_config_home $default_xdg_config_home\
xdg_data_home $default_xdg_data_home\ xdg_data_home $default_xdg_data_home\
xdg_cache_home $default_xdg_cache_home\ xdg_cache_home $default_xdg_cache_home\
xdg_state_home $default_xdg_state_home\ xdg_state_home $default_xdg_state_home\
xdg_data_dirs $default_xdg_data_dirs\ xdg_data_dirs $default_xdg_data_dirs\
theme_posh_override ""\ theme_posh_override ""\
posh_theme ""\ posh_theme ""\
posh_themes_path ""\ posh_themes_path ""\
] ]
set startup $defaults set startup $defaults
#load values from saved config file - $xdg_config_home/punk/punk.config ? #load values from saved config file - $xdg_config_home/punk/punk.config ?
#typically we want env vars to override the stored config - as env vars conventionally used on some commandlines. #typically we want env vars to override the stored config - as env vars conventionally used on some commandlines.
#that's possibly ok for the PUNK_ vars #that's possibly ok for the PUNK_ vars
#however.. others like the xdg vars and NOCOLOR may apply to other apps.. and we may want to override them from the saved config? #however.. others like the xdg vars and NOCOLOR may apply to other apps.. and we may want to override them from the saved config?
#making some env vars override saved config values and some not would be potentially confusing. may need one/more specific settings or env vars to determine which takes precedence? #making some env vars override saved config values and some not would be potentially confusing. may need one/more specific settings or env vars to determine which takes precedence?
#simpler is probably just to let env vars take precedence - and warn when saving or viewing config that the saved values are being overridden #simpler is probably just to let env vars take precedence - and warn when saving or viewing config that the saved values are being overridden
#- requiring user to manually unset any unwanted env vars when launching? #- requiring user to manually unset any unwanted env vars when launching?
#we are likely to want the saved configs for subshells/decks to override them however. #we are likely to want the saved configs for subshells/decks to override them however.
#todo - load/save config file #todo - load/save config file
#todo - define which configvars are settable in env #todo - define which configvars are settable in env
#list of varname varinfo where varinfo is a sub dictionary (type key is mandatory, with value from: string,pathlist,boolean) #list of varname varinfo where varinfo is a sub dictionary (type key is mandatory, with value from: string,pathlist,boolean)
set punk_env_vars_config [dict create \ set punk_env_vars_config [dict create \
PUNK_APPS {type pathlist}\ PUNK_APPS {type pathlist}\
PUNK_CONFIG {type string}\ PUNK_CONFIG {type string}\
PUNK_CONFIGSET {type string}\ PUNK_CONFIGSET {type string}\
PUNK_SCRIPTLIB {type string}\ PUNK_SCRIPTLIB {type string}\
PUNK_AUTO_EXEC_MECHANISM {type string}\ PUNK_AUTO_EXEC_MECHANISM {type string}\
PUNK_AUTO_NOEXEC {type string default 0 help "set 1 to set Tcl's ::auto_noexec true.\nStops 'unknown' from running external programs"}\ PUNK_AUTO_NOEXEC {type string default 0 help "set 1 to set Tcl's ::auto_noexec true.\nStops 'unknown' from running external programs"}\
PUNK_COLOR_STDERR {type string help "stderr colour transform. Use 'punk::ansi::a?' to see colour names"}\ PUNK_COLOR_STDERR {type string help "stderr colour transform. Use 'punk::ansi::a?' to see colour names"}\
PUNK_COLOR_STDERR_REPL {type string help "stderr colour transform only while command running (not active during 'after')"}\ PUNK_COLOR_STDERR_REPL {type string help "stderr colour transform only while command running (not active during 'after')"}\
PUNK_COLOR_STDOUT {type string help "stdout colour transform. Use 'punk::ansi::a?' to see colour names"}\ PUNK_COLOR_STDOUT {type string help "stdout colour transform. Use 'punk::ansi::a?' to see colour names"}\
PUNK_COLOR_STDOUT_REPL {type string help "stdout colour transform only while command running (not active during 'after')"}\ PUNK_COLOR_STDOUT_REPL {type string help "stdout colour transform only while command running (not active during 'after')"}\
PUNK_LOGFILE_STDOUT {type string}\ PUNK_LOGFILE_STDOUT {type string}\
PUNK_LOGFILE_STDERR {type string}\ PUNK_LOGFILE_STDERR {type string}\
PUNK_LOGFILE_ACTIVE {type string}\ PUNK_LOGFILE_ACTIVE {type string}\
PUNK_SYSLOG_STDOUT {type string}\ PUNK_SYSLOG_STDOUT {type string}\
PUNK_SYSLOG_STDERR {type string}\ PUNK_SYSLOG_STDERR {type string}\
PUNK_SYSLOG_ACTIVE {type string}\ PUNK_SYSLOG_ACTIVE {type string}\
PUNK_THEME_POSH_OVERRIDE {type string}\ PUNK_THEME_POSH_OVERRIDE {type string}\
] ]
set punk_env_vars [dict keys $punk_env_vars_config] set punk_env_vars [dict keys $punk_env_vars_config]
#override with env vars if set #override with env vars if set
foreach {evar varinfo} $punk_env_vars_config { foreach {evar varinfo} $punk_env_vars_config {
if {[info exists ::env($evar)]} { if {[info exists ::env($evar)]} {
set vartype [dict get $varinfo type] set vartype [dict get $varinfo type]
set f [set ::env($evar)] set f [set ::env($evar)]
if {$f ne "default"} { if {$f ne "default"} {
#e.g PUNK_SCRIPTLIB -> scriptlib #e.g PUNK_SCRIPTLIB -> scriptlib
set varname [tcl::string::tolower [tcl::string::range $evar 5 end]] set varname [tcl::string::tolower [tcl::string::range $evar 5 end]]
if {$vartype eq "pathlist"} { if {$vartype eq "pathlist"} {
#colon vs semicolon path sep is problematic for windows environments where unix-like systems such as cygwin/wsl are used and a variable may be set for either the native path separator or the unix-like system #colon vs semicolon path sep is problematic for windows environments where unix-like systems such as cygwin/wsl are used and a variable may be set for either the native path separator or the unix-like system
#Even without the colon vs semicolon issue, native vs unix-like paths on windows mixed environment systems can cause grief. #Even without the colon vs semicolon issue, native vs unix-like paths on windows mixed environment systems can cause grief.
#For now at least, we will simply respect the platform pathSeparator and hope the user manages the environment variables appropriately. #For now at least, we will simply respect the platform pathSeparator and hope the user manages the environment variables appropriately.
#some programs do automatic translation - which is a nice idea in principle - but is also prone to error as we don't know if it's already occurred or not depending on how things are launched. #some programs do automatic translation - which is a nice idea in principle - but is also prone to error as we don't know if it's already occurred or not depending on how things are launched.
#An example of where this sort of thing can go wrong is env(TCLLIBPATH) - which is defined as a space separated list not requiring further splitting #An example of where this sort of thing can go wrong is env(TCLLIBPATH) - which is defined as a space separated list not requiring further splitting
# - but some programs have been known to split this value on colon anyway, which breaks things on windows. # - but some programs have been known to split this value on colon anyway, which breaks things on windows.
set paths [split $f $::tcl_platform(pathSeparator)] set paths [split $f $::tcl_platform(pathSeparator)]
set final [list] set final [list]
#eliminate empty values (leading or trailing or extraneous separators) #eliminate empty values (leading or trailing or extraneous separators)
foreach p $paths { foreach p $paths {
if {[tcl::string::trim $p] ne ""} { if {[tcl::string::trim $p] ne ""} {
lappend final $p lappend final $p
} }
} }
tcl::dict::set startup $varname $final tcl::dict::set startup $varname $final
} else { } else {
tcl::dict::set startup $varname $f tcl::dict::set startup $varname $f
} }
} }
} }
} }
# https://no-color.org # https://no-color.org
#if {[info exists ::env(NO_COLOR)]} { #if {[info exists ::env(NO_COLOR)]} {
# if {$::env(NO_COLOR) ne ""} { # if {$::env(NO_COLOR) ne ""} {
# set colour_disabled 1 # set colour_disabled 1
# } # }
#} #}
set other_env_vars_config [dict create\ set other_env_vars_config [dict create\
NO_COLOR {type string}\ NO_COLOR {type string}\
XDG_CONFIG_HOME {type string}\ XDG_CONFIG_HOME {type string}\
XDG_DATA_HOME {type string}\ XDG_DATA_HOME {type string}\
XDG_CACHE_HOME {type string}\ XDG_CACHE_HOME {type string}\
XDG_STATE_HOME {type string}\ XDG_STATE_HOME {type string}\
XDG_DATA_DIRS {type pathlist}\ XDG_DATA_DIRS {type pathlist}\
POSH_THEME {type string}\ POSH_THEME {type string}\
POSH_THEMES_PATH {type string}\ POSH_THEMES_PATH {type string}\
TCLLIBPATH {type string}\ TCLLIBPATH {type string}\
] ]
lassign [split [info tclversion] .] tclmajorv tclminorv lassign [split [info tclversion] .] tclmajorv tclminorv
#don't rely on lseq or punk::lib for now.. #don't rely on lseq or punk::lib for now..
set relevant_minors [list] set relevant_minors [list]
for {set i 0} {$i <= $tclminorv} {incr i} { for {set i 0} {$i <= $tclminorv} {incr i} {
lappend relevant_minors $i lappend relevant_minors $i
} }
foreach minor $relevant_minors { foreach minor $relevant_minors {
set vname TCL${tclmajorv}_${minor}_TM_PATH set vname TCL${tclmajorv}_${minor}_TM_PATH
if {$minor eq $tclminorv || [info exists ::env($vname)]} { if {$minor eq $tclminorv || [info exists ::env($vname)]} {
dict set other_env_vars_config $vname {type string} dict set other_env_vars_config $vname {type string}
} }
} }
set other_env_vars [dict keys $other_env_vars_config] set other_env_vars [dict keys $other_env_vars_config]
foreach {evar varinfo} $other_env_vars_config { foreach {evar varinfo} $other_env_vars_config {
if {[info exists ::env($evar)]} { if {[info exists ::env($evar)]} {
set vartype [dict get $varinfo type] set vartype [dict get $varinfo type]
set f [set ::env($evar)] set f [set ::env($evar)]
if {$f ne "default"} { if {$f ne "default"} {
set varname [tcl::string::tolower $evar] set varname [tcl::string::tolower $evar]
if {$vartype eq "pathlist"} { if {$vartype eq "pathlist"} {
set paths [split $f $::tcl_platform(pathSeparator)] set paths [split $f $::tcl_platform(pathSeparator)]
set final [list] set final [list]
#eliminate empty values (leading or trailing or extraneous separators) #eliminate empty values (leading or trailing or extraneous separators)
foreach p $paths { foreach p $paths {
if {[tcl::string::trim $p] ne ""} { if {[tcl::string::trim $p] ne ""} {
lappend final $p lappend final $p
} }
} }
tcl::dict::set startup $varname $final tcl::dict::set startup $varname $final
} else { } else {
tcl::dict::set startup $varname $f tcl::dict::set startup $varname $f
} }
} }
} }
} }
#unset -nocomplain vars #unset -nocomplain vars
#todo #todo
set running [tcl::dict::create] set running [tcl::dict::create]
set running [tcl::dict::merge $running $startup] set running [tcl::dict::merge $running $startup]
} }
init init
#todo #todo
proc Apply {config} { proc Apply {config} {
puts stderr "punk::config::Apply partially implemented" puts stderr "punk::config::Apply partially implemented"
set configname [string map {-config ""} $config] set configname [string map {-config ""} $config]
if {$configname in {startup running}} { if {$configname in {startup running}} {
upvar ::punk::config::$configname applyconfig upvar ::punk::config::$configname applyconfig
if {[dict exists $applyconfig auto_noexec]} { if {[dict exists $applyconfig auto_noexec]} {
set auto [dict get $applyconfig auto_noexec] set auto [dict get $applyconfig auto_noexec]
if {![string is boolean -strict $auto]} { if {![string is boolean -strict $auto]} {
error "config::Apply error - invalid data for auto_noexec:'$auto' - expected boolean" error "config::Apply error - invalid data for auto_noexec:'$auto' - expected boolean"
} }
if {$auto} { if {$auto} {
set ::auto_noexec 1 set ::auto_noexec 1
} else { } else {
#puts "auto_noexec false" #puts "auto_noexec false"
unset -nocomplain ::auto_noexec unset -nocomplain ::auto_noexec
} }
} }
} else { } else {
error "no config named '$config' found" error "no config named '$config' found"
} }
return "apply done" return "apply done"
} }
Apply startup Apply startup
#todo - consider how to divide up settings, categories, 'devices', decks etc #todo - consider how to divide up settings, categories, 'devices', decks etc
proc get_running_global {varname} { proc get_running_global {varname} {
variable running variable running
if {[dict exists $running $varname]} { if {[dict exists $running $varname]} {
return [dict get $running $varname] return [dict get $running $varname]
} }
error "No such global configuration item '$varname' found in running config" error "No such global configuration item '$varname' found in running config"
} }
proc get_startup_global {varname} { proc get_startup_global {varname} {
variable startup variable startup
if {[dict exists $startup $varname]} { if {[dict exists $startup $varname]} {
return [dict get $startup $varname] return [dict get $startup $varname]
} }
error "No such global configuration item '$varname' found in startup config" error "No such global configuration item '$varname' found in startup config"
} }
proc get {whichconfig {globfor *}} { proc get {whichconfig {globfor *}} {
variable startup variable startup
variable running variable running
switch -- $whichconfig { switch -- $whichconfig {
config - startup - startup-config - startup-configuration { config - startup - startup-config - startup-configuration {
#show *startup* config - different behaviour may be confusing to those used to router startup and running configs #show *startup* config - different behaviour may be confusing to those used to router startup and running configs
set configdata $startup set configdata $startup
} }
running - running-config - running-configuration { running - running-config - running-configuration {
set configdata $running set configdata $running
} }
default { default {
error "Unknown config name '$whichconfig' - try startup or running" error "Unknown config name '$whichconfig' - try startup or running"
} }
} }
if {$globfor eq "*"} { if {$globfor eq "*"} {
return $configdata return $configdata
} else { } else {
set keys [dict keys $configdata [string tolower $globfor]] set keys [dict keys $configdata [string tolower $globfor]]
set filtered [dict create] set filtered [dict create]
foreach k $keys { foreach k $keys {
dict set filtered $k [dict get $configdata $k] dict set filtered $k [dict get $configdata $k]
} }
return $filtered return $filtered
} }
} }
proc configure {args} { proc configure {args} {
set argdef { set argdef {
@id -id ::punk::config::configure @id -id ::punk::config::configure
@cmd -name punk::config::configure -help\ @cmd -name punk::config::configure -help\
"UNIMPLEMENTED" "UNIMPLEMENTED"
@values -min 1 -max 1 @values -min 1 -max 1
whichconfig -type string -choices {startup running stop} whichconfig -type string -choices {startup running stop}
} }
set argd [punk::args::get_dict $argdef $args] set argd [punk::args::get_dict $argdef $args]
return "unimplemented - $argd" return "unimplemented - $argd"
} }
proc show {whichconfig {globfor *}} { proc show {whichconfig {globfor *}} {
#todo - tables for console #todo - tables for console
set configdata [punk::config::get $whichconfig $globfor] set configdata [punk::config::get $whichconfig $globfor]
return [punk::lib::showdict $configdata] return [punk::lib::showdict $configdata]
} }
#e.g #e.g
# copy running-config startup-config # copy running-config startup-config
# copy startup-config test-config.cfg # copy startup-config test-config.cfg
# copy backup-config.cfg running-config # copy backup-config.cfg running-config
#review - consider the merge vs overwrite feature of some routers.. where copy to running-config does a merge rather than an overwrite #review - consider the merge vs overwrite feature of some routers.. where copy to running-config does a merge rather than an overwrite
#This is to allow partial configs to be loaded to running, whereas a save of running to any target is always a complete configuration #This is to allow partial configs to be loaded to running, whereas a save of running to any target is always a complete configuration
proc copy {args} { proc copy {args} {
set argdef { set argdef {
@id -id ::punk::config::copy @id -id ::punk::config::copy
@cmd -name punk::config::copy -help\ @cmd -name punk::config::copy -help\
"Copy a partial or full configuration from one config to another "Copy a partial or full configuration from one config to another
If a target config has additional settings, then the source config can be considered to be partial with regards to the target. If a target config has additional settings, then the source config can be considered to be partial with regards to the target.
" "
-type -default "" -choices {replace merge} -help\ -type -default "" -choices {replace merge} -help\
"Defaults to merge when target is running-config "Defaults to merge when target is running-config
Defaults to replace when source is running-config" Defaults to replace when source is running-config"
@values -min 2 -max 2 @values -min 2 -max 2
fromconfig -help\ fromconfig -help\
"running or startup or file name (not fully implemented)" "running or startup or file name (not fully implemented)"
toconfig -help\ toconfig -help\
"running or startup or file name (not fully implemented)" "running or startup or file name (not fully implemented)"
} }
set argd [punk::args::get_dict $argdef $args] set argd [punk::args::get_dict $argdef $args]
set fromconfig [dict get $argd values fromconfig] set fromconfig [dict get $argd values fromconfig]
set toconfig [dict get $argd values toconfig] set toconfig [dict get $argd values toconfig]
set fromconfig [string map {-config ""} $fromconfig] set fromconfig [string map {-config ""} $fromconfig]
set toconfig [string map {-config ""} $toconfig] set toconfig [string map {-config ""} $toconfig]
set copytype [dict get $argd opts -type] set copytype [dict get $argd opts -type]
#todo - warn & prompt if doing merge copy to startup #todo - warn & prompt if doing merge copy to startup
switch -exact -- $fromconfig-$toconfig { switch -exact -- $fromconfig-$toconfig {
running-startup { running-startup {
if {$copytype eq ""} { if {$copytype eq ""} {
set copytype replace ;#full configuration set copytype replace ;#full configuration
} }
if {$copytype eq "replace"} { if {$copytype eq "replace"} {
error "punk::config::copy error. full configuration copy from running to startup config not yet supported" error "punk::config::copy error. full configuration copy from running to startup config not yet supported"
} else { } else {
error "punk::config::copy error. merge configuration copy from running to startup config not yet supported" error "punk::config::copy error. merge configuration copy from running to startup config not yet supported"
} }
} }
startup-running { startup-running {
#default type merge - even though it's not always what is desired #default type merge - even though it's not always what is desired
if {$copytype eq ""} { if {$copytype eq ""} {
set copytype merge ;#load in a partial configuration set copytype merge ;#load in a partial configuration
} }
#warn/prompt either way #warn/prompt either way
if {$copytype eq "replace"} { if {$copytype eq "replace"} {
#some routers require use of a separate command for this branch. #some routers require use of a separate command for this branch.
#presumably to ensure the user doesn't accidentally load partials onto a running system #presumably to ensure the user doesn't accidentally load partials onto a running system
# #
error "punk::config::copy error. full configuration copy from startup to overwrite running config not supported" error "punk::config::copy error. full configuration copy from startup to overwrite running config not supported"
} else { } else {
error "punk::config::copy error. merge copy from possibly partial configuration: startup to running config not currently supported" error "punk::config::copy error. merge copy from possibly partial configuration: startup to running config not currently supported"
} }
} }
default { default {
error "punk::config::copy error. copy must from running to startup or startup to running. File sources/targets not yet supported" error "punk::config::copy error. copy must from running to startup or startup to running. File sources/targets not yet supported"
} }
} }
} }
} }
#todo - move to cli? #todo - move to cli?
::tcl::namespace::eval punk::config { ::tcl::namespace::eval punk::config {
#todo - something better - 'previous' rather than reverting to startup #todo - something better - 'previous' rather than reverting to startup
proc channelcolors {{onoff {}}} { proc channelcolors {{onoff {}}} {
variable running variable running
variable startup variable startup
if {![string length $onoff]} { if {![string length $onoff]} {
return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]] return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]]
} else { } else {
if {![string is boolean $onoff]} { if {![string is boolean $onoff]} {
error "channelcolors: invalid value $onoff - expected boolean: true|false|on|off|1|0|yes|no" error "channelcolors: invalid value $onoff - expected boolean: true|false|on|off|1|0|yes|no"
} }
if {$onoff} { if {$onoff} {
dict set running color_stdout [dict get $startup color_stdout] dict set running color_stdout [dict get $startup color_stdout]
dict set running color_stderr [dict get $startup color_stderr] dict set running color_stderr [dict get $startup color_stderr]
} else { } else {
dict set running color_stdout "" dict set running color_stdout ""
dict set running color_stderr "" dict set running color_stderr ""
} }
} }
return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]] return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]]
} }
} }
package provide punk::config [tcl::namespace::eval punk::config { package provide punk::config [tcl::namespace::eval punk::config {
variable version variable version
set version 0.1 set version 0.1
}] }]

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

@ -767,6 +767,8 @@ namespace eval punk::mix::base {
dict for {path pathinfo} $dict_path_cksum { dict for {path pathinfo} $dict_path_cksum {
puts "fill_relativecksums_from_base_and_relativepathdict-->$path REVIEW"
#review to see if we process same path repeatedly, so could avoid repeated 'file exists $fullpath' below by caching a glob
if {![dict exists $pathinfo cksum]} { if {![dict exists $pathinfo cksum]} {
dict set pathinfo cksum "" dict set pathinfo cksum ""
} else { } else {
@ -851,7 +853,7 @@ namespace eval punk::mix::base {
} }
} else { } else {
if {[file type $specifiedpath] eq "relative"} { if {[file pathtype $specifiedpath] eq "relative"} {
#if specifiedpath is relative - and we don't have a base, we now need to convert relative to cwd to an absolute path for storage #if specifiedpath is relative - and we don't have a base, we now need to convert relative to cwd to an absolute path for storage
set targetpath [file normalize $specifiedpath] set targetpath [file normalize $specifiedpath]
set storedpath $targetpath set storedpath $targetpath
@ -911,6 +913,7 @@ namespace eval punk::mix::base {
} }
#buildruntime.exe obsolete.. #buildruntime.exe obsolete..
puts stderr "warning obsolete? get_all_vfs_build_cksums 'buildruntime.exe'???"
set fullpath_buildruntime $buildfolder/buildruntime.exe set fullpath_buildruntime $buildfolder/buildruntime.exe
set ckinfo_buildruntime [cksum_path $fullpath_buildruntime] set ckinfo_buildruntime [cksum_path $fullpath_buildruntime]

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

@ -412,9 +412,9 @@ namespace eval punk::mix::cli {
set repopaths [punk::repo::find_repos [pwd]] set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos] set repos [dict get $repopaths repos]
if {![llength $repos]} { if {![llength $repos]} {
append result [dict get $repopaths warnings] append result [punk::ansi::a+ bold yellow][dict get $repopaths warnings][punk::ansi::a]
} else { } else {
append result [dict get $repopaths warnings] append result [punk::ansi::a+ bold yellow][dict get $repopaths warnings][punk::ansi::a]
lassign [lindex $repos 0] repopath repotypes lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} { if {"fossil" in $repotypes} {
#review - multiple process launches to fossil a bit slow on windows.. #review - multiple process launches to fossil a bit slow on windows..
@ -739,7 +739,7 @@ namespace eval punk::mix::cli {
} }
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "P"
set did_skip 1 set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record] #set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$build_event targetset_end SKIPPED $build_event targetset_end SKIPPED
@ -771,7 +771,7 @@ namespace eval punk::mix::cli {
$event targetset_end OK -note "zip modpod" $event targetset_end OK -note "zip modpod"
} }
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "p"
set did_skip 1 set did_skip 1
if {$is_interesting} { if {$is_interesting} {
puts stderr "$modulefile [$event targetset_source_changes]" puts stderr "$modulefile [$event targetset_source_changes]"
@ -893,7 +893,7 @@ namespace eval punk::mix::cli {
if {$is_interesting} { if {$is_interesting} {
puts stdout "skipping module $current_source_dir/$m - no change in sources detected" puts stdout "skipping module $current_source_dir/$m - no change in sources detected"
} }
puts -nonewline stderr "." puts -nonewline stderr "m"
set did_skip 1 set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record] #set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$event targetset_end SKIPPED $event targetset_end SKIPPED
@ -935,7 +935,7 @@ namespace eval punk::mix::cli {
#set file_record [punkcheck::installfile_finished_install $basedir $file_record] #set file_record [punkcheck::installfile_finished_install $basedir $file_record]
$event targetset_end OK -note "already versioned module" $event targetset_end OK -note "already versioned module"
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "f"
set did_skip 1 set did_skip 1
if {$is_interesting} { if {$is_interesting} {
puts stderr "$current_source_dir/$m [$event targetset_source_changes]" puts stderr "$current_source_dir/$m [$event targetset_source_changes]"
@ -951,7 +951,8 @@ namespace eval punk::mix::cli {
if {$CALLDEPTH >= $max_depth} { if {$CALLDEPTH >= $max_depth} {
set subdirs [list] set subdirs [list]
} else { } else {
set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *] set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *]
set targets_existing [glob -nocomplain -dir $target_module_dir -type d -tail {*}$subdirs]
} }
#puts stderr "subdirs: $subdirs" #puts stderr "subdirs: $subdirs"
foreach d $subdirs { foreach d $subdirs {
@ -965,7 +966,10 @@ namespace eval punk::mix::cli {
if {$skipdir} { if {$skipdir} {
continue continue
} }
if {![file exists $target_module_dir/$d]} { #if {![file exists $target_module_dir/$d]} {
# file mkdir $target_module_dir/$d
#}
if {$d ni $targets_existing} {
file mkdir $target_module_dir/$d file mkdir $target_module_dir/$d
} }
lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\ lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\

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

@ -49,7 +49,7 @@ namespace eval punk::mix::commandset::buildsuite {
set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok. set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok.
set s [lindex $path_parts end-1] set s [lindex $path_parts end-1]
set p [lindex $path_parts end] set p [lindex $path_parts end]
#This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl #This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl
#so we can't just use tail as dict key. We could assume last record is always total - but #so we can't just use tail as dict key. We could assume last record is always total - but
if {![string match -nocase $s $suite]} { if {![string match -nocase $s $suite]} {

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

@ -26,7 +26,7 @@ namespace eval punk::mix::commandset::debug {
namespace export get paths namespace export get paths
namespace path ::punk::mix::cli namespace path ::punk::mix::cli
#Except for 'get' - all debug commands should emit to stdout #Except for 'get' - all debug commands should emit to stdout
proc paths {} { proc paths {} {
set out "" set out ""
puts stdout "find_repos output:" puts stdout "find_repos output:"
@ -40,7 +40,7 @@ namespace eval punk::mix::commandset::debug {
set template_base_dict [punk::mix::base::lib::get_template_basefolders] set template_base_dict [punk::mix::base::lib::get_template_basefolders]
puts stdout "get_template_basefolders output:" puts stdout "get_template_basefolders output:"
pdict template_base_dict */* pdict template_base_dict */*
return return
} }
#call other debug command - but capture stdout as return value #call other debug command - but capture stdout as return value
@ -84,9 +84,9 @@ namespace eval punk::mix::commandset::debug {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug { package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

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

@ -26,8 +26,10 @@ namespace eval punk::mix::commandset::module {
namespace export * namespace export *
proc paths {} { proc paths {} {
set roots [punk::repo::find_repos ""] #set roots [punk::repo::find_repos ""]
set project [lindex [dict get $roots project] 0] #set project [lindex [dict get $roots project] 0]
set project [punk::repo::find_project ""]
if {$project ne ""} { if {$project ne ""} {
set is_project 1 set is_project 1
set searchbase $project set searchbase $project

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

@ -20,7 +20,7 @@
#[manpage_begin punkshell_module_punk::mix::commandset::project 0 0.1.0] #[manpage_begin punkshell_module_punk::mix::commandset::project 0 0.1.0]
#[copyright "2023"] #[copyright "2023"]
#[titledesc {dec commandset - project}] [comment {-- Name section and table of contents description --}] #[titledesc {dec commandset - project}] [comment {-- Name section and table of contents description --}]
#[moddesc {deck CLI commandset - project}] [comment {-- Description at end of page heading --}] #[moddesc {deck CLI commandset - project}] [comment {-- Description at end of page heading --}]
#[require punk::mix::commandset::project] #[require punk::mix::commandset::project]
#[description] #[description]
@ -29,25 +29,25 @@
#*** !doctools #*** !doctools
#[section Overview] #[section Overview]
#[para] overview of punk::mix::commandset::project #[para] overview of punk::mix::commandset::project
#[para]Import into an ensemble namespace similarly to the way it is done with punk::mix::cli e.g #[para]Import into an ensemble namespace similarly to the way it is done with punk::mix::cli e.g
#[example { #[example {
# namespace eval myproject::cli { # namespace eval myproject::cli {
# namespace export * # namespace export *
# namespace ensemble create # namespace ensemble create
# package require punk::overlay # package require punk::overlay
# #
# package require punk::mix::commandset::project # package require punk::mix::commandset::project
# punk::overlay::import_commandset project . ::punk::mix::commandset::project # punk::overlay::import_commandset project . ::punk::mix::commandset::project
# punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection # punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
# } # }
#}] #}]
#[para] Where the . in the above example is the prefix/command separator #[para] Where the . in the above example is the prefix/command separator
#[para]The prefix ('project' in the above example) can be any string desired to disambiguate commands imported from other commandsets. #[para]The prefix ('project' in the above example) can be any string desired to disambiguate commands imported from other commandsets.
#[para]The above results in the availability of the ensemble command: ::myproject::cli project.new, which is implemented in ::punk::mix::commandset::project::new #[para]The above results in the availability of the ensemble command: ::myproject::cli project.new, which is implemented in ::punk::mix::commandset::project::new
#[para]Similarly, procs under ::punk::mix::commandset::project::collection will be available as subcommands of the ensemble as <ensemblecommand> projects.<procname> #[para]Similarly, procs under ::punk::mix::commandset::project::collection will be available as subcommands of the ensemble as <ensemblecommand> projects.<procname>
#[para] #[para]
#[subsection Concepts] #[subsection Concepts]
#[para] see punk::overlay #[para] see punk::overlay
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -56,7 +56,7 @@
#*** !doctools #*** !doctools
#[subsection dependencies] #[subsection dependencies]
#[para] packages used by punk::mix::commandset::project #[para] packages used by punk::mix::commandset::project
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
@ -88,7 +88,7 @@ namespace eval punk::mix::commandset::project {
namespace export * namespace export *
#*** !doctools #*** !doctools
#[subsection {Namespace punk::mix::commandset::project}] #[subsection {Namespace punk::mix::commandset::project}]
#[para] core commandset functions for punk::mix::commandset::project #[para] core commandset functions for punk::mix::commandset::project
#[list_begin definitions] #[list_begin definitions]
proc _default {} { proc _default {} {
@ -133,7 +133,7 @@ namespace eval punk::mix::commandset::project {
proc new {newprojectpath_or_name args} { proc new {newprojectpath_or_name args} {
#*** !doctools #*** !doctools
# [call [fun new] [arg newprojectpath_or_name] [opt args]] # [call [fun new] [arg newprojectpath_or_name] [opt args]]
#new project structure - may be dedicated to one module, or contain many. #new project structure - may be dedicated to one module, or contain many.
#create minimal folder structure only by specifying in args: -modules {} #create minimal folder structure only by specifying in args: -modules {}
if {[file pathtype $newprojectpath_or_name] eq "absolute"} { if {[file pathtype $newprojectpath_or_name] eq "absolute"} {
set projectfullpath [file normalize $newprojectpath_or_name] set projectfullpath [file normalize $newprojectpath_or_name]
@ -185,7 +185,7 @@ namespace eval punk::mix::commandset::project {
if {$opt_force || $opt_update} { if {$opt_force || $opt_update} {
#generally undesirable to add default project module during an update. #generally undesirable to add default project module during an update.
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
@ -207,12 +207,12 @@ namespace eval punk::mix::commandset::project {
} }
#we don't assume 'unknown' is configured to run shell commands #we don't assume 'unknown' is configured to run shell commands
if {[string length [package provide shellrun]]} { if {[string length [package provide shellrun]]} {
set exitinfo [run {*}$scoop_prog install fossil] set exitinfo [run {*}$scoop_prog install fossil]
#scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use. #scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use.
puts stdout "scoop install fossil ran with result: $exitinfo" puts stdout "scoop install fossil ran with result: $exitinfo"
} else { } else {
puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )" puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )"
set result [exec {*}$scoop_prog install fossil] set result [exec {*}$scoop_prog install fossil]
puts stdout $result puts stdout $result
} }
catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"') catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"')
@ -304,7 +304,7 @@ namespace eval punk::mix::commandset::project {
} }
} }
set project_dir_exists [file exists $projectdir] set project_dir_exists [file exists $projectdir]
if {$project_dir_exists && !($opt_force || $opt_update)} { if {$project_dir_exists && !($opt_force || $opt_update)} {
puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template" puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template"
@ -332,7 +332,7 @@ namespace eval punk::mix::commandset::project {
puts stderr $warnmsg puts stderr $warnmsg
} }
set fossil_repo_file "" set fossil_repo_file ""
set is_fossil_root 0 set is_fossil_root 0
if {$project_dir_exists && [punk::repo::is_fossil_root $projectdir]} { if {$project_dir_exists && [punk::repo::is_fossil_root $projectdir]} {
set is_fossil_root 1 set is_fossil_root 1
@ -356,7 +356,7 @@ namespace eval punk::mix::commandset::project {
return return
} }
#review #review
set fossil_repo_file $repodb_folder/$projectname.fossil set fossil_repo_file $repodb_folder/$projectname.fossil
} }
if {$fossil_repo_file eq ""} { if {$fossil_repo_file eq ""} {
@ -378,7 +378,7 @@ namespace eval punk::mix::commandset::project {
file mkdir $projectdir file mkdir $projectdir
puts stdout ">>> about to call punkcheck::install $layout_path $projectdir" puts stdout ">>> about to call punkcheck::install $layout_path $projectdir"
set resultdict [dict create] set resultdict [dict create]
set antipaths [list\ set antipaths [list\
src/doc/*\ src/doc/*\
@ -394,10 +394,10 @@ 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 #default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized
if {$opt_force} { if {$opt_force} {
puts stdout "copying layout files - with force applied - overwrite all-targets" puts stdout "copying layout files - with force applied - overwrite all-targets"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -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 { } else {
puts stdout "copying layout files - (if source file changed)" puts stdout "copying layout files - (if source file changed)"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -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] puts stdout [punkcheck::summarize_install_resultdict $resultdict]
@ -410,10 +410,10 @@ namespace eval punk::mix::commandset::project {
puts stdout "no src/doc in source template - update not required" 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. #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. #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*"] ## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"]
set override_antiglob_dir_core [list #* _aside .git] set override_antiglob_dir_core [list #* _aside .git]
if {[file exists $layout_path/.fossil-custom]} { if {[file exists $layout_path/.fossil-custom]} {
puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)" 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 -createempty 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]
@ -430,9 +430,9 @@ namespace eval punk::mix::commandset::project {
puts stdout "no .fossil-settings in source template - update not required" puts stdout "no .fossil-settings in source template - update not required"
} }
#scan all files in template #scan all files in template
# #
#TODO - deck command to substitute templates? #TODO - deck command to substitute templates?
set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout]
set stripprefix [file normalize $layout_path] set stripprefix [file normalize $layout_path]
@ -440,7 +440,7 @@ namespace eval punk::mix::commandset::project {
if {[llength $templatefiles]} { if {[llength $templatefiles]} {
puts stdout "Filling template file placeholders with the following tag map:" puts stdout "Filling template file placeholders with the following tag map:"
foreach {placeholder value} $tagmap { foreach {placeholder value} $tagmap {
puts stdout " $placeholder -> $value" puts stdout " $placeholder -> $value"
} }
} }
foreach templatefullpath $templatefiles { foreach templatefullpath $templatefiles {
@ -452,7 +452,7 @@ namespace eval punk::mix::commandset::project {
set data2 [string map $tagmap $data] set data2 [string map $tagmap $data]
if {$data2 ne $data} { if {$data2 ne $data} {
puts stdout "updated template file: $fpath" puts stdout "updated template file: $fpath"
set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data2; close $fdout set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data2; close $fdout
} }
} else { } else {
puts stderr "warning: Missing template file $fpath" puts stderr "warning: Missing template file $fpath"
@ -464,7 +464,7 @@ namespace eval punk::mix::commandset::project {
if {[file exists $projectdir/src/modules]} { if {[file exists $projectdir/src/modules]} {
foreach m $opt_modules { foreach m $opt_modules {
#check if mod-ver.tm file or #modpod-mod-ver folder exist #check if mod-ver.tm file or #modpod-mod-ver folder exist
set tmfile $projectdir/src/modules/$m-[punk::mix::util::magic_tm_version].tm set tmfile $projectdir/src/modules/$m-[punk::mix::util::magic_tm_version].tm
set podfile $projectdir/src/modules/#modpod-$m-[punk::mix::util::magic_tm_version]/$m-[punk::mix::util::magic_tm_version].tm set podfile $projectdir/src/modules/#modpod-$m-[punk::mix::util::magic_tm_version]/$m-[punk::mix::util::magic_tm_version].tm
@ -482,7 +482,7 @@ namespace eval punk::mix::commandset::project {
set overwrite_type zip set overwrite_type zip
} else { } else {
set answer [util::askuser "OVERWRITE the src/modules file $tmfile ?? (generally not desirable) Y|N"] set answer [util::askuser "OVERWRITE the src/modules file $tmfile ?? (generally not desirable) Y|N"]
set overwrite_type $opt_type set overwrite_type $opt_type
} }
if {[string tolower $answer] eq "y"} { if {[string tolower $answer] eq "y"} {
#REVIEW - all pods zip - for now #REVIEW - all pods zip - for now
@ -503,7 +503,7 @@ namespace eval punk::mix::commandset::project {
$installer set_source_target $projectdir/src/doc $projectdir/src/embedded $installer set_source_target $projectdir/src/doc $projectdir/src/embedded
set event [$installer start_event {-install_step kettledoc}] set event [$installer start_event {-install_step kettledoc}]
$event targetset_init VIRTUAL kettle_build_doc ;#VIRTUAL - since there is no specific target file - and we don't know all the files that will be generated $event targetset_init VIRTUAL kettle_build_doc ;#VIRTUAL - since there is no specific target file - and we don't know all the files that will be generated
$event targetset_addsource $projectdir/src/doc ;#whole doc tree is considered the source $event targetset_addsource $projectdir/src/doc ;#whole doc tree is considered the source
#---------- #----------
if {\ if {\
[llength [dict get [$event targetset_source_changes] changed]]\ [llength [dict get [$event targetset_source_changes] changed]]\
@ -535,7 +535,7 @@ namespace eval punk::mix::commandset::project {
if {![punk::repo::is_fossil_root $projectdir]} { if {![punk::repo::is_fossil_root $projectdir]} {
set first_fossil 1 set first_fossil 1
#-k = keep. (only modify the manifest file(s)) #-k = keep. (only modify the manifest file(s))
if {$is_nested_fossil} { if {$is_nested_fossil} {
set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir] set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir]
} else { } else {
@ -600,11 +600,11 @@ namespace eval punk::mix::commandset::project {
#[para]The glob argument is optional unless option/value pairs are also supplied, in which case * should be explicitly supplied #[para]The glob argument is optional unless option/value pairs are also supplied, in which case * should be explicitly supplied
#[para]glob restricts output based on the name of the fossil db file e.g s* for all projects beginning with s #[para]glob restricts output based on the name of the fossil db file e.g s* for all projects beginning with s
#[para]The _default function is made available in the ensemble by the name of the prefix used when importing the commandset. #[para]The _default function is made available in the ensemble by the name of the prefix used when importing the commandset.
#[para]e.g #[para]e.g
#[para] punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection #[para] punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
#[para]Will result in the command being available as <ensemblecommand> projects #[para]Will result in the command being available as <ensemblecommand> projects
package require overtype package require overtype
set db_projects [lib::get_projects $glob] set db_projects [lib::get_projects $glob]
set col1items [lsearch -all -inline -index 0 -subindices $db_projects *] set col1items [lsearch -all -inline -index 0 -subindices $db_projects *]
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] set col2items [lsearch -all -inline -index 1 -subindices $db_projects *]
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *]
@ -620,15 +620,15 @@ namespace eval punk::mix::commandset::project {
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]] set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]]
set col3 [string repeat " " $widest3] set col3 [string repeat " " $widest3]
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}] set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}]
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n
append msg [string repeat "=" $tablewidth] \n append msg [string repeat "=" $tablewidth] \n
foreach p $col1items n $col2items c $col3items { foreach p $col1items n $col2items c $col3items {
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n
} }
return $msg return $msg
#return [list_as_lines [lib::get_projects $glob]] #return [list_as_lines [lib::get_projects $glob]]
} }
proc detail {{glob {}} args} { proc detail {{glob {}} args} {
package require overtype package require overtype
@ -640,14 +640,14 @@ namespace eval punk::mix::commandset::project {
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_description [dict get $opts -description] set opt_description [dict get $opts -description]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set db_projects [lib::get_projects $glob]
set db_projects [lib::get_projects $glob]
set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *] set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *]
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] set col2items [lsearch -all -inline -index 1 -subindices $db_projects *]
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *]
set col3items [lmap v $checkouts {llength $v}] set col3items [lmap v $checkouts {llength $v}]
set col4_pnames [list] set col4_pnames [list]
set col5_pcodes [list] set col5_pcodes [list]
set col6_dupids [list] set col6_dupids [list]
@ -658,13 +658,13 @@ namespace eval punk::mix::commandset::project {
set project_name "" set project_name ""
set project_code "" set project_code ""
set project_desc "" set project_desc ""
set db_error "" set db_error ""
if {[file exists $dbfile]} { if {[file exists $dbfile]} {
if {[catch { if {[catch {
sqlite3 dbp $dbfile sqlite3 dbp $dbfile
dbp eval {select name,value from config where name like 'project-%';} r { dbp eval {select name,value from config where name like 'project-%';} r {
if {$r(name) eq "project-name"} { if {$r(name) eq "project-name"} {
set project_name $r(value) set project_name $r(value)
} elseif {$r(name) eq "project-code"} { } elseif {$r(name) eq "project-code"} {
set project_code $r(value) set project_code $r(value)
} elseif {$r(name) eq "project-description"} { } elseif {$r(name) eq "project-description"} {
@ -687,7 +687,7 @@ namespace eval punk::mix::commandset::project {
} }
incr file_idx incr file_idx
} }
set setid 1 set setid 1
set codeset [dict create] set codeset [dict create]
dict for {code dbs} $codes { dict for {code dbs} $codes {
@ -696,17 +696,17 @@ namespace eval punk::mix::commandset::project {
dict set codeset $code count [llength $dbs] dict set codeset $code count [llength $dbs]
dict set codeset $code seen 0 dict set codeset $code seen 0
incr setid incr setid
} }
} }
set dupid 1 set dupid 1
foreach pc $col5_pcodes { foreach pc $col5_pcodes {
if {[dict exists $codeset $pc]} { if {[dict exists $codeset $pc]} {
set seen [dict get $codeset $pc seen] set seen [dict get $codeset $pc seen]
set this_seen [expr {$seen + 1}] set this_seen [expr {$seen + 1}]
dict set codeset $pc seen $this_seen dict set codeset $pc seen $this_seen
lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]" lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]"
} else { } else {
lappend col6_dupids "" lappend col6_dupids ""
} }
} }
@ -732,10 +732,10 @@ namespace eval punk::mix::commandset::project {
#set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {string length $v}]] #set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {string length $v}]]
set widest7 35 set widest7 35
set col7 [string repeat " " $widest7] set col7 [string repeat " " $widest7]
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}] set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}]
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\ append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\
[overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]" [overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]"
if {!$opt_description} { if {!$opt_description} {
@ -747,7 +747,7 @@ namespace eval punk::mix::commandset::project {
append msg [string repeat "=" $tablewidth] \n append msg [string repeat "=" $tablewidth] \n
foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs { foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs {
set desclines [split [textutil::adjust $desc -length $widest7] \n] set desclines [split [textutil::adjust $desc -length $widest7] \n]
set desc1 [lindex $desclines 0] set desc1 [lindex $desclines 0]
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\ append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\
[overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]" [overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]"
@ -756,20 +756,20 @@ namespace eval punk::mix::commandset::project {
} else { } else {
append msg " [overtype::left $col7 $desc1]" \n append msg " [overtype::left $col7 $desc1]" \n
foreach dline [lrange $desclines 1 end] { foreach dline [lrange $desclines 1 end] {
append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n
} }
} }
} }
return $msg return $msg
#return [list_as_lines [lib::get_projects $glob]] #return [list_as_lines [lib::get_projects $glob]]
} }
proc cd {{glob {}} args} { proc cd {{glob {}} args} {
dict set args -cd 1 dict set args -cd 1
work $glob {*}$args work $glob {*}$args
} }
proc work {{glob {}} args} { proc work {{glob {}} args} {
package require sqlite3 package require sqlite3
set db_projects [lib::get_projects $glob] set db_projects [lib::get_projects $glob]
if {[llength $db_projects] == 0} { if {[llength $db_projects] == 0} {
puts stderr "::punk::mix::commandset::project::work No Repo DB name matches found for '$glob'" puts stderr "::punk::mix::commandset::project::work No Repo DB name matches found for '$glob'"
return "" return ""
@ -779,22 +779,22 @@ namespace eval punk::mix::commandset::project {
set defaults [dict create\ set defaults [dict create\
-cd 0\ -cd 0\
-detail "\uFFFF"\ -detail "\uFFFF"\
] ]
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_cd [dict get $opts -cd] set opt_cd [dict get $opts -cd]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_detail [dict get $opts -detail] set opt_detail [dict get $opts -detail]
set opt_detail_explicit_zero 1 ;#default assumption only set opt_detail_explicit_zero 1 ;#default assumption only
if {$opt_detail eq "\uFFFF"} { if {$opt_detail eq "\uFFFF"} {
set opt_detail_explicit_zero 0 set opt_detail_explicit_zero 0
set opt_detail 0; #default set opt_detail 0; #default
} }
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set workdir_dict [dict create] set workdir_dict [dict create]
set all_workdirs [list] set all_workdirs [list]
foreach pinfo $db_projects { foreach pinfo $db_projects {
lassign $pinfo fosdb name workdirs lassign $pinfo fosdb name workdirs
foreach wdir $workdirs { foreach wdir $workdirs {
dict set workdir_dict $wdir $pinfo dict set workdir_dict $wdir $pinfo
lappend all_workdirs $wdir lappend all_workdirs $wdir
@ -808,15 +808,15 @@ namespace eval punk::mix::commandset::project {
set col_pcodes [list] set col_pcodes [list]
set col_dupids [list] set col_dupids [list]
set fosdb_count [dict create] set fosdb_count [dict create]
set fosdb_dupset [dict create] set fosdb_dupset [dict create]
set fosdb_cache [dict create] set fosdb_cache [dict create]
set dupset 0 set dupset 0
set rowid 1 set rowid 1
foreach wd $workdirs { foreach wd $workdirs {
set wdinfo [dict get $workdir_dict $wd] set wdinfo [dict get $workdir_dict $wd]
lassign $wdinfo fosdb nm siblingworkdirs lassign $wdinfo fosdb nm siblingworkdirs
dict incr fosdb_count $fosdb dict incr fosdb_count $fosdb
set dbcount [dict get $fosdb_count $fosdb] set dbcount [dict get $fosdb_count $fosdb]
if {[llength $siblingworkdirs] > 1} { if {[llength $siblingworkdirs] > 1} {
if {![dict exists $fosdb_dupset $fosdb]} { if {![dict exists $fosdb_dupset $fosdb]} {
@ -825,7 +825,7 @@ namespace eval punk::mix::commandset::project {
} }
set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]" set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]"
} else { } else {
set dupid "" set dupid ""
} }
if {$dbcount == 1} { if {$dbcount == 1} {
set pname "" set pname ""
@ -842,7 +842,7 @@ namespace eval punk::mix::commandset::project {
puts stderr "!!! error: $errM" puts stderr "!!! error: $errM"
} }
} else { } else {
puts stderr "!!! missing fossil db $fosdb" puts stderr "!!! missing fossil db $fosdb"
} }
} else { } else {
set info [dict get $fosdb_cache $fosdb] set info [dict get $fosdb_cache $fosdb]
@ -858,7 +858,7 @@ namespace eval punk::mix::commandset::project {
set col_states [list] set col_states [list]
set state_title "" set state_title ""
#if only one set of fossil checkouts in the resultset and opt_detail is 0 and not explicit - retrieve workingdir state for each co #if only one set of fossil checkouts in the resultset and opt_detail is 0 and not explicit - retrieve workingdir state for each co
if {([llength [dict keys $fosdb_cache]] == 1)} { if {([llength [dict keys $fosdb_cache]] == 1)} {
if {!$opt_detail_explicit_zero} { if {!$opt_detail_explicit_zero} {
set opt_detail 1 set opt_detail 1
@ -884,13 +884,13 @@ namespace eval punk::mix::commandset::project {
set state_dict [punk::repo::workingdir_state_summary_dict $wd_state] set state_dict [punk::repo::workingdir_state_summary_dict $wd_state]
lappend c_rev [string range [dict get $state_dict revision] 0 9] lappend c_rev [string range [dict get $state_dict revision] 0 9]
lappend c_rev_iso [dict get $state_dict revision_iso8601] lappend c_rev_iso [dict get $state_dict revision_iso8601]
lappend c_unchanged [dict get $state_dict unchanged] lappend c_unchanged [dict get $state_dict unchanged]
lappend c_changed [dict get $state_dict changed] lappend c_changed [dict get $state_dict changed]
lappend c_new [dict get $state_dict new] lappend c_new [dict get $state_dict new]
lappend c_missing [dict get $state_dict missing] lappend c_missing [dict get $state_dict missing]
lappend c_extra [dict get $state_dict extra] lappend c_extra [dict get $state_dict extra]
puts -nonewline stderr "." puts -nonewline stderr "."
} }
puts -nonewline stderr \n puts -nonewline stderr \n
set t0 "Revision" set t0 "Revision"
set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]] set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]]
@ -913,13 +913,13 @@ namespace eval punk::mix::commandset::project {
set t5 "Extr" set t5 "Extr"
set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]] set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]]
set c5 [string repeat " " $w5] set c5 [string repeat " " $w5]
set state_title "[overtype::left $c0 $t0] [overtype::left $c0b $t0b] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]" set state_title "[overtype::left $c0 $t0] [overtype::left $c0b $t0b] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]"
foreach r $c_rev iso $c_rev_iso u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra { foreach r $c_rev iso $c_rev_iso u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra {
lappend col_states "[overtype::left $c0 $r] [overtype::left $c0b $iso] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]" lappend col_states "[overtype::left $c0 $r] [overtype::left $c0b $iso] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]"
} }
} }
set msg "" set msg ""
if {$opt_cd} { if {$opt_cd} {
set title0 "CD" set title0 "CD"
@ -948,7 +948,7 @@ namespace eval punk::mix::commandset::project {
append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]" append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]"
if {[llength $col_states]} { if {[llength $col_states]} {
set title6 $state_title set title6 $state_title
set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]] set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]]
set col6 [string repeat " " $widest6] set col6 [string repeat " " $widest6]
incr tablewidth [expr {$widest6 + 1}] incr tablewidth [expr {$widest6 + 1}]
@ -965,7 +965,7 @@ namespace eval punk::mix::commandset::project {
set wd [punk::ansi::a+ red]$wd[a] set wd [punk::ansi::a+ red]$wd[a]
} }
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} { if {![file exists $wd]} {
@ -973,7 +973,7 @@ namespace eval punk::mix::commandset::project {
set wd [punk::ansi::a+ red]$wd[a] set wd [punk::ansi::a+ red]$wd[a]
} }
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }
set numrows [llength $col_rowids] set numrows [llength $col_rowids]
if {$opt_cd && $numrows >= 1} { if {$opt_cd && $numrows >= 1} {
@ -985,7 +985,7 @@ namespace eval punk::mix::commandset::project {
::cd $workingdir ::cd $workingdir
return $workingdir return $workingdir
} else { } else {
puts stderr "path $workingdir doesn't appear to exist" puts stderr "path $workingdir doesn't appear to exist"
return [pwd] return [pwd]
} }
} else { } else {
@ -1004,12 +1004,12 @@ namespace eval punk::mix::commandset::project {
#*** !doctools #*** !doctools
#[list_end] [comment {-- end collection namespace definitions --}] #[list_end] [comment {-- end collection namespace definitions --}]
} }
namespace eval lib { namespace eval lib {
proc template_tag {tagname} { proc template_tag {tagname} {
#todo - support different tagwrappers - it shouldn't be so likely to collide with common code idioms etc. #todo - support different tagwrappers - it shouldn't be so likely to collide with common code idioms etc.
#we need to detect presence of tags intended for punk::mix system #we need to detect presence of tags intended for punk::mix system
#consider using punk::cap to enable multiple template-substitution providers with their own set of tagnames and/or tag wrappers, where substitution providers are all run #consider using punk::cap to enable multiple template-substitution providers with their own set of tagnames and/or tag wrappers, where substitution providers are all run
return [string cat % $tagname %] return [string cat % $tagname %]
} }
#get project info only by opening the central confg-db #get project info only by opening the central confg-db
@ -1032,12 +1032,13 @@ namespace eval punk::mix::commandset::project {
set path [string trim [string range $pr 5 end]] set path [string trim [string range $pr 5 end]]
set nm [file rootname [file tail $path]] set nm [file rootname [file tail $path]]
set ckouts [fosconf eval {select name from global_config where value = $path;}] set ckouts [fosconf eval {select name from global_config where value = $path;}]
#list of entries like "ckout:C:/buildtcl/2024zig/tcl90/"
set checkout_paths [list] set checkout_paths [list]
#strip "ckout:" #strip "ckout:"
foreach ck $ckouts { foreach ck $ckouts {
lappend checkout_paths [string trim [string range $ck 6 end]] lappend checkout_paths [string trim [string range $ck 6 end]]
} }
lappend paths_and_names [list $path $nm $checkout_paths] lappend paths_and_names [list $path $nm $checkout_paths]
} }
set filtered_list [list] set filtered_list [list]
foreach glob $globlist { foreach glob $globlist {
@ -1045,16 +1046,14 @@ namespace eval punk::mix::commandset::project {
foreach m $matches { foreach m $matches {
if {$m ni $filtered_list} { if {$m ni $filtered_list} {
lappend filtered_list $m lappend filtered_list $m
} }
} }
} }
set projects [lsort -index 1 $filtered_list] set projects [lsort -index 1 $filtered_list]
return $projects return $projects
} }
} }
@ -1067,15 +1066,10 @@ namespace eval punk::mix::commandset::project {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project { package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

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

@ -24,6 +24,9 @@
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::mix::commandset::repo { namespace eval punk::mix::commandset::repo {
namespace export * namespace export *
variable PUNKARGS
proc tickets {{project ""}} { proc tickets {{project ""}} {
#todo #todo
set result "" set result ""
@ -52,9 +55,9 @@ namespace eval punk::mix::commandset::repo {
set repopaths [punk::repo::find_repos [pwd]] set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos] set repos [dict get $repopaths repos]
if {![llength $repos]} { if {![llength $repos]} {
append result [dict get $repopaths warnings] append result [a+ bold yellow][dict get $repopaths warnings][a]
} else { } else {
append result [dict get $repopaths warnings] append result [a+ bold yellow][dict get $repopaths warnings][a]
lassign [lindex $repos 0] repopath repotypes lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} { if {"fossil" in $repotypes} {
append result \n "Fossil repo based at $repopath" append result \n "Fossil repo based at $repopath"
@ -69,6 +72,17 @@ namespace eval punk::mix::commandset::repo {
} }
return $result return $result
} }
#punk::args
lappend PUNKARGS [list {
@id -id ::punk::mix::commandset::repo::fossil-move-repository
@cmd -name punk::mix::commandset::repo::fossil-move-repository -help\
"Move the fossil repository file (usually named with .fossil extension).
This is an interactive function which will prompt for answers on stdin
before proceeding.
The move can be done even if there are open checkouts and will maintain
the link between checkout databases and the repository file."
}]
proc fossil-move-repository {{path ""}} { proc fossil-move-repository {{path ""}} {
set searchbase [pwd] set searchbase [pwd]
set projectinfo [punk::repo::find_repos $searchbase] set projectinfo [punk::repo::find_repos $searchbase]
@ -281,7 +295,7 @@ namespace eval punk::mix::commandset::repo {
set ckouts [oldrepo eval {select name from config where name like 'ckout:%'}] set ckouts [oldrepo eval {select name from config where name like 'ckout:%'}]
oldrepo close oldrepo close
if {[llength $ckouts] > 1} { if {[llength $ckouts] > 1} {
puts stdout "There are [llength $ckouts] checkouts for the repository you are moving" puts stdout "There are [llength $ckouts] checkouts for the repository you are moving"
puts stdout "You will be asked for each checkout if you want to adjust it to point to $target_repodb_folder/$pname2.folder" puts stdout "You will be asked for each checkout if you want to adjust it to point to $target_repodb_folder/$pname2.folder"
} }
set original_cwd [pwd] set original_cwd [pwd]
@ -304,11 +318,11 @@ namespace eval punk::mix::commandset::repo {
puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}" puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}"
puts stderr "$moveresult" puts stderr "$moveresult"
} else { } else {
puts stdout "OK - move performed with result:" puts stdout "OK - move performed with result:"
puts stdout $moveresult puts stdout $moveresult
} }
} }
} }
cd $original_cwd cd $original_cwd
} }
@ -379,7 +393,7 @@ namespace eval punk::mix::commandset::repo {
puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}" puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}"
puts stderr "$moveresult" puts stderr "$moveresult"
} else { } else {
puts stdout "OK - move performed with result:" puts stdout "OK - move performed with result:"
puts stdout $moveresult puts stdout $moveresult
} }
} }
@ -402,10 +416,10 @@ namespace eval punk::mix::commandset::repo {
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::mix::commandset::repo
}
@ -413,9 +427,9 @@ namespace eval punk::mix::commandset::repo {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::repo [namespace eval punk::mix::commandset::repo { package provide punk::mix::commandset::repo [namespace eval punk::mix::commandset::repo {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

7
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/templates/utility/a b/tcltest.bat

@ -1,7 +0,0 @@
::lindex tcl;#\
@call tclsh "%~dp0%~n0.bat" %* & goto :eof
# --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl
puts stdout "script: [info script]"
puts stdout "argv: $::argc"
puts stdout "args: '$::argv'"

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

@ -1,164 +1,163 @@
#punkapps app manager #punkapps app manager
# deck cli # deck cli
namespace eval punk::mod::cli { namespace eval punk::mod::cli {
namespace export help list run namespace export help list run
namespace ensemble create namespace ensemble create
# namespace ensemble configure [namespace current] -unknown punk::mod::cli::_unknown # namespace ensemble configure [namespace current] -unknown punk::mod::cli::_unknown
if 0 { if 0 {
proc _unknown {ns args} { proc _unknown {ns args} {
puts stderr "punk::mod::cli::_unknown '$ns' '$args'" puts stderr "punk::mod::cli::_unknown '$ns' '$args'"
puts stderr "punk::mod::cli::help $args" puts stderr "punk::mod::cli::help $args"
puts stderr "arglen:[llength $args]" puts stderr "arglen:[llength $args]"
punk::mod::cli::help {*}$args punk::mod::cli::help {*}$args
} }
} }
#cli must have _init method - usually used to load commandsets lazily #cli must have _init method - usually used to load commandsets lazily
# #
variable initialised 0 variable initialised 0
proc _init {args} { proc _init {args} {
variable initialised variable initialised
if {$initialised} { if {$initialised} {
return return
} }
#... #...
set initialised 1 set initialised 1
} }
proc help {args} { proc help {args} {
set basehelp [punk::mix::base help {*}$args] set basehelp [punk::mix::base help {*}$args]
#namespace export #namespace export
return $basehelp return $basehelp
} }
proc getraw {appname} { proc getraw {appname} {
upvar ::punk::config::running running_config upvar ::punk::config::running running_config
set app_folders [dict get $running_config apps] set app_folders [dict get $running_config apps]
#todo search each app folder #todo search each app folder
set bases [::list] set bases [::list]
set versions [::list] set versions [::list]
set mains [::list] set mains [::list]
set appinfo [::list bases {} mains {} versions {}] set appinfo [::list bases {} mains {} versions {}]
foreach containerfolder $app_folders { foreach containerfolder $app_folders {
lappend bases $containerfolder lappend bases $containerfolder
if {[file exists $containerfolder]} { if {[file exists $containerfolder]} {
if {[file exists $containerfolder/$appname/main.tcl]} { if {[file exists $containerfolder/$appname/main.tcl]} {
#exact match - only return info for the exact one specified #exact match - only return info for the exact one specified
set namematches $appname set namematches $appname
set parts [split $appname -] set parts [split $appname -]
} else { } else {
set namematches [glob -nocomplain -dir $containerfolder -type d -tail ${appname}-*] set namematches [glob -nocomplain -dir $containerfolder -type d -tail ${appname}-*]
set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort? set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort?
} }
foreach nm $namematches { foreach nm $namematches {
set mainfile $containerfolder/$nm/main.tcl set mainfile $containerfolder/$nm/main.tcl
set parts [split $nm -] set parts [split $nm -]
if {[llength $parts] == 1} { if {[llength $parts] == 1} {
set ver "" set ver ""
} else { } else {
set ver [lindex $parts end] set ver [lindex $parts end]
} }
if {$ver ni $versions} { if {$ver ni $versions} {
lappend versions $ver lappend versions $ver
lappend mains $ver $mainfile lappend mains $ver $mainfile
} else { } else {
puts stderr "punk::apps::app version '$ver' of app '$appname' already encountered at $mainfile. (will use earliest encountered in running-config apps and ignore others of same version)" puts stderr "punk::apps::app version '$ver' of app '$appname' already encountered at $mainfile. (will use earliest encountered in running-config apps and ignore others of same version)"
} }
} }
} else { } else {
puts stderr "punk::apps::app missing apps_folder:'$containerfolder' Ensure apps_folder is set in punk::config" puts stderr "punk::apps::app missing apps_folder:'$containerfolder' Ensure apps_folder is set in punk::config"
} }
} }
dict set appinfo versions $versions dict set appinfo versions $versions
#todo - natsort! #todo - natsort!
set sorted_versions [lsort $versions] set sorted_versions [lsort $versions]
set latest [lindex $sorted_versions 0] set latest [lindex $sorted_versions 0]
if {$latest eq "" && [llength $sorted_versions] > 1} { if {$latest eq "" && [llength $sorted_versions] > 1} {
set latest [lindex $sorted_versions 1 set latest [lindex $sorted_versions 1]
} }
dict set appinfo latest $latest dict set appinfo latest $latest
dict set appinfo bases $bases dict set appinfo bases $bases
dict set appinfo mains $mains dict set appinfo mains $mains
return $appinfo return $appinfo
} }
proc list {{glob *}} { proc list {{glob *}} {
upvar ::punk::config::running running_config upvar ::punk::config::running running_config
set apps_folder [dict get $running_config apps] set apps_folder [dict get $running_config apps]
if {[file exists $apps_folder]} { if {[file exists $apps_folder]} {
if {[file exists $apps_folder/$glob]} { if {[file exists $apps_folder/$glob]} {
#tailcall source $apps_folder/$glob/main.tcl #tailcall source $apps_folder/$glob/main.tcl
return $glob return $glob
} }
set apps [glob -nocomplain -dir $apps_folder -type d -tail $glob] set apps [glob -nocomplain -dir $apps_folder -type d -tail $glob]
if {[llength $apps] == 0} { if {[llength $apps] == 0} {
if {[string first * $glob] <0 && [string first ? $glob] <0} { if {[string first * $glob] <0 && [string first ? $glob] <0} {
#no glob chars supplied - only launch if exact match for name part #no glob chars supplied - only launch if exact match for name part
set namematches [glob -nocomplain -dir $apps_folder -type d -tail ${glob}-*] set namematches [glob -nocomplain -dir $apps_folder -type d -tail ${glob}-*]
set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort? set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort?
if {[llength $namematches] > 0} { if {[llength $namematches] > 0} {
set latest [lindex $namematches end] set latest [lindex $namematches end]
lassign $latest nm ver lassign $latest nm ver
#tailcall source $apps_folder/$latest/main.tcl #tailcall source $apps_folder/$latest/main.tcl
} }
} }
} }
return $apps return $apps
} }
} }
#todo - way to launch as separate process #todo - way to launch as separate process
# solo-opts only before appname - args following appname are passed to the app # solo-opts only before appname - args following appname are passed to the app
proc run {args} { proc run {args} {
set nameposn [lsearch -not $args -*] set nameposn [lsearch -not $args -*]
if {$nameposn < 0} { if {$nameposn < 0} {
error "punkapp::run unable to determine application name" error "punkapp::run unable to determine application name"
} }
set appname [lindex $args $nameposn] set appname [lindex $args $nameposn]
set controlargs [lrange $args 0 $nameposn-1] set controlargs [lrange $args 0 $nameposn-1]
set appargs [lrange $args $nameposn+1 end] set appargs [lrange $args $nameposn+1 end]
set appinfo [punk::mod::cli::getraw $appname] set appinfo [punk::mod::cli::getraw $appname]
if {[llength [dict get $appinfo versions]]} { if {[llength [dict get $appinfo versions]]} {
set ver [dict get $appinfo latest] set ver [dict get $appinfo latest]
puts stdout "info: $appinfo" puts stdout "info: $appinfo"
set ::argc [llength $appargs] set ::argc [llength $appargs]
set ::argv $appargs set ::argv $appargs
source [dict get $appinfo mains $ver] source [dict get $appinfo mains $ver]
if {"-hideconsole" in $controlargs} { if {"-hideconsole" in $controlargs} {
puts stderr "attempting console hide" puts stderr "attempting console hide"
#todo - something better - a callback when window mapped? #todo - something better - a callback when window mapped?
after 500 {::punkapp::hide_console} after 500 {::punkapp::hide_console}
} }
return $appinfo return $appinfo
} else { } else {
error "punk::mod::cli unable to run '$appname'. main.tcl not found in [dict get $appinfo bases]" error "punk::mod::cli unable to run '$appname'. main.tcl not found in [dict get $appinfo bases]"
} }
} }
} }
namespace eval punk::mod::cli { namespace eval punk::mod::cli {
proc _cli {args} { proc _cli {args} {
#don't use tailcall - base uses info level to determine caller #don't use tailcall - base uses info level to determine caller
::punk::mix::base::_cli {*}$args ::punk::mix::base::_cli {*}$args
} }
variable default_command help variable default_command help
package require punk::mix::base package require punk::mix::base
package require punk::overlay package require punk::overlay
punk::overlay::custom_from_base [namespace current] ::punk::mix::base punk::overlay::custom_from_base [namespace current] ::punk::mix::base
} }
package provide punk::mod [namespace eval punk::mod { package provide punk::mod [namespace eval punk::mod {
variable version variable version
set version 0.1 set version 0.1
}]
}]

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

@ -657,6 +657,7 @@ namespace eval punk::path {
**/_aside (exlude files where _aside is last segment) **/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder) **/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)" **/_aside/** (exclude all folders with _aside as a segment)"
-antiglob_files -default {}
@values -min 0 -max -1 -optional 1 -type string @values -min 0 -max -1 -optional 1 -type string
tailglobs -default * -multiple 1 -help\ tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path "Patterns to match against filename portion (last segment) of each file path
@ -681,6 +682,7 @@ namespace eval punk::path {
set tailglobs [dict get $values tailglobs] set tailglobs [dict get $values tailglobs]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths] set opt_antiglob_paths [dict get $opts -antiglob_paths]
set opt_antiglob_files [dict get $opts -antiglob_files]
set CALLDEPTH [dict get $opts -call-depth-internal] set CALLDEPTH [dict get $opts -call-depth-internal]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
@ -718,7 +720,24 @@ namespace eval punk::path {
puts stderr "treefilenames error while listing files in dir $opt_dir\n $matches" puts stderr "treefilenames error while listing files in dir $opt_dir\n $matches"
set dirfiles [list] set dirfiles [list]
} else { } else {
set dirfiles [lsort $matches] set retained [list]
if {[llength $opt_antiglob_files]} {
foreach m $matches {
set skip 0
set ftail [file tail $m]
foreach anti $opt_antiglob_files {
if {[string match $anti $ftail]} {
set skip 1; break
}
}
if {!$skip} {
lappend retained $m
}
}
} else {
set retained $matches
}
set dirfiles [lsort $retained]
} }
lappend files {*}$dirfiles lappend files {*}$dirfiles

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

@ -39,16 +39,16 @@ if {$::tcl_platform(platform) eq "windows"} {
} }
package require fileutil; #tcllib package require fileutil; #tcllib
package require punk::path package require punk::path
package require punk::mix::base ;#uses core functions from punk::mix::base::lib namespace e.g cksum_path package require punk::mix::base ;#uses core functions from punk::mix::base::lib namespace e.g cksum_path
package require punk::mix::util ;#do_in_path package require punk::mix::util ;#do_in_path
# -- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- ---
# For performance/efficiency reasons - use file functions on paths in preference to string operations # For performance/efficiency reasons - use file functions on paths in preference to string operations
# e.g use file join # e.g use file join
# branch to avoid unnecessary calls to 'pwd' or 'file normalize' - which can be surprisingly expensive operations (as at tcl 8.7 2023) # branch to avoid unnecessary calls to 'pwd' or 'file normalize' - which can be surprisingly expensive operations (as at tcl 8.7 2023)
# pwd is only expensive if we treat it as a string instead of a list/path # pwd is only expensive if we treat it as a string instead of a list/path
# e.g # e.g
# > time {set x [pwd]} # > time {set x [pwd]}
# 5 microsoeconds.. no problem # 5 microsoeconds.. no problem
# > time {set x [pwd]} # > time {set x [pwd]}
@ -67,11 +67,11 @@ namespace eval punk::repo {
variable cached_command_paths variable cached_command_paths
set cached_command_paths [dict create] set cached_command_paths [dict create]
#anticipating possible removal of buggy caching from auto_execok #anticipating possible removal of buggy caching from auto_execok
#mentioned in: https://core.tcl-lang.org/tcl/tktview/4dc35e0c0c #mentioned in: https://core.tcl-lang.org/tcl/tktview/4dc35e0c0c
#this would leave the application to decide what it wants to cache in that regard. #this would leave the application to decide what it wants to cache in that regard.
proc Cached_auto_execok {name} { proc Cached_auto_execok {name} {
return [auto_execok $name] return [auto_execok $name]
#variable cached_command_paths #variable cached_command_paths
#if {[dict exists $cached_command_paths $name]} { #if {[dict exists $cached_command_paths $name]} {
# return [dict get $cached_command_paths $name] # return [dict get $cached_command_paths $name]
@ -102,14 +102,14 @@ namespace eval punk::repo {
"" {${$othercmds}} "" {${$othercmds}}
} }
}] }]
return $result return $result
} }
#lappend PUNKARGS [list { #lappend PUNKARGS [list {
# @dynamic # @dynamic
# @id -id ::punk::repo::fossil_proxy # @id -id ::punk::repo::fossil_proxy
# @cmd -name fossil -help "fossil executable # @cmd -name fossil -help "fossil executable
# " # "
# @argdisplay -header "fossil help" -body {${[runout -n fossil help]}} # @argdisplay -header "fossil help" -body {${[runout -n fossil help]}}
@ -117,7 +117,7 @@ namespace eval punk::repo {
lappend PUNKARGS [list { lappend PUNKARGS [list {
@dynamic @dynamic
@id -id ::punk::repo::fossil_proxy @id -id ::punk::repo::fossil_proxy
@cmd -name fossil -help "fossil executable" @cmd -name fossil -help "fossil executable"
${[punk::repo::get_fossil_usage]} ${[punk::repo::get_fossil_usage]}
} ] } ]
@ -128,14 +128,13 @@ namespace eval punk::repo {
lappend PUNKARGS [list { lappend PUNKARGS [list {
@dynamic @dynamic
@id -id "::punk::repo::fossil_proxy diff" @id -id "::punk::repo::fossil_proxy diff"
@cmd -name "fossil diff" -help "fossil diff @cmd -name "fossil diff" -help "fossil diff"
"
@argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}} @argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}}
} ""] } ""]
lappend PUNKARGS [list { lappend PUNKARGS [list {
#todo - remove this comment - testing dynamic directive #todo - remove this comment - testing dynamic directive
@dynamic @dynamic
@id -id "::punk::repo::fossil_proxy add" @id -id "::punk::repo::fossil_proxy add"
@cmd -name "fossil add" -help "fossil add @cmd -name "fossil add" -help "fossil add
" "
@argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}} @argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
@ -152,16 +151,16 @@ namespace eval punk::repo {
lappend PUNKARGS_aliases {"::fossil diff" "::punk::repo::fossil_proxy diff"} lappend PUNKARGS_aliases {"::fossil diff" "::punk::repo::fossil_proxy diff"}
#Todo - investigate proper way to install a client-side commit hook in the fossil project #Todo - investigate proper way to install a client-side commit hook in the fossil project
#Then we may still use this proxy to check the hook - but the required checks will occur when another shell used #Then we may still use this proxy to check the hook - but the required checks will occur when another shell used
proc fossil_proxy {args} { proc fossil_proxy {args} {
set start_dir [pwd] set start_dir [pwd]
set fosroot [find_fossil $start_dir] set fosroot [find_fossil $start_dir]
set fossilcmd [lindex $args 0] set fossilcmd [lindex $args 0]
set no_warning_commands [list "help" "dbstat" "grep" "diff" "xdiff" "cat" "version"] set no_warning_commands [list "help" "dbstat" "grep" "diff" "xdiff" "cat" "version"]
if {$fossilcmd ni $no_warning_commands } { if {$fossilcmd ni $no_warning_commands } {
set repostate [find_repos $start_dir] set repostate [find_repos $start_dir]
} }
set no_prompt_commands [list "status" "info" {*}$no_warning_commands] set no_prompt_commands [list "status" "info" {*}$no_warning_commands]
@ -170,7 +169,7 @@ namespace eval punk::repo {
if {$fossilcmd ni $no_prompt_commands} { if {$fossilcmd ni $no_prompt_commands} {
set fossilrepos [dict get $repostate fossil] set fossilrepos [dict get $repostate fossil]
if {[llength $fossilrepos] > 1} { if {[llength $fossilrepos] > 1} {
puts stdout [dict get $repostate warnings] puts stdout [punk::ansi::a+ bold yellow][dict get $repostate warnings][punk::ansi::a]
puts stdout "Operating on inner fossil repository: [lindex $fossilrepos 0]" puts stdout "Operating on inner fossil repository: [lindex $fossilrepos 0]"
puts stdout "Use FOSSIL instead of fossil to avoid this prompt and warning" puts stdout "Use FOSSIL instead of fossil to avoid this prompt and warning"
set answer [askuser "Are you sure you want to perform the operation on this repo? Y/N"] set answer [askuser "Are you sure you want to perform the operation on this repo? Y/N"]
@ -217,7 +216,7 @@ namespace eval punk::repo {
} }
} elseif {$fossilcmd in [list "info" "status"]} { } elseif {$fossilcmd in [list "info" "status"]} {
#emit warning whether or not multiple fossil repos #emit warning whether or not multiple fossil repos
puts stdout [dict get $repostate warnings] puts stdout [punk::ansi::a+ bold yellow][dict get $repostate warnings][punk::ansi::a]
} }
set fossil_prog [Cached_auto_execok fossil] set fossil_prog [Cached_auto_execok fossil]
if {$fossil_prog ne ""} { if {$fossil_prog ne ""} {
@ -234,7 +233,7 @@ namespace eval punk::repo {
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
#catch { #catch {
# if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
# interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
# } # }
#} #}
@ -245,7 +244,7 @@ namespace eval punk::repo {
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy #uppercase FOSSIL to bypass fossil as alias to fossil_proxy
#only necessary on unix? #only necessary on unix?
#Windows filesystem case insensitive so any non-lowercase fossil version goes out to get an ::auto_execs entry anyway #Windows filesystem case insensitive so any non-lowercase fossil version goes out to get an ::auto_execs entry anyway
proc establish_FOSSIL {args} { proc establish_FOSSIL {args} {
#review #review
if {![info exists ::auto_execs(FOSSIL)]} { if {![info exists ::auto_execs(FOSSIL)]} {
@ -298,7 +297,7 @@ namespace eval punk::repo {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
scanup $path is_fossil_root scanup $path is_fossil_root
} }
proc find_git {{path {}}} { proc find_git {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
scanup $path is_git_root scanup $path is_git_root
@ -330,12 +329,31 @@ namespace eval punk::repo {
} }
} }
} }
lappend PUNKARGS [list {
@id -id "::punk::repo::find_project"
@cmd -name "punk::repo::find_project" -help\
"Find and return the path for the root of
the project to which the supplied path belongs.
If the supplied path is empty, the current
working directory is used as the starting point
for the upwards search.
Returns nothing if there is no project at or
above the specified path."
@values -min 0 -max 1
path -optional 1 -default "" -help\
"May be an absolute or relative path.
The full specified path doesn't have
to exist. The code will walk upwards
along the segments of the supplied path
testing the result of 'is_project_root'."
}]
proc find_project {{path {}}} { proc find_project {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
scanup $path is_project_root scanup $path is_project_root
} }
proc is_fossil_root {{path {}}} { #detect if path is a fossil root - without consulting fossil databases
proc is_fossil_root2 {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
#from kettle::path::is.fossil #from kettle::path::is.fossil
foreach control { foreach control {
@ -348,20 +366,51 @@ namespace eval punk::repo {
} }
return 0 return 0
} }
proc is_fossil_root {{path {}}} {
#much faster on windows than 'file exists' checks
if {$path eq {}} { set path [pwd] }
set control [list _FOSSIL_ .fslckout .fos]
#could be marked 'hidden' on windows
if {"windows" eq $::tcl_platform(platform)} {
set files [list {*}[glob -nocomplain -dir $path -types f -tail {*}$control] {*}[glob -nocomplain -dir $path -types {f hidden} -tail {*}$control]]
} else {
set files [glob -nocomplain -dir $path -types f -tail {*}$control]
}
expr {[llength $files] > 0}
}
#review - is a .git folder sufficient? #review - is a .git folder sufficient?
#consider git rev-parse --git-dir ? #consider git rev-parse --git-dir ?
proc is_git_root {{path {}}} { proc is_git_root {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
set control [file join $path .git] #set control [file join $path .git]
expr {[file exists $control] && [file isdirectory $control]} #expr {[file exists $control] && [file isdirectory $control]}
if {"windows" eq $::tcl_platform(platform)} {
#:/
#globbing for dotfiles in tcl is problematic across platforms - windows 'hidden' concept is independent
#we need to find .git whether hidden or not - so need 2 glob operations
#.git may or may not be set with windows 'hidden' attribute
set hiddengitdir [glob -nocomplain -dir $path -types {d hidden} -tail .git]
set nonhiddengitdir [glob -nocomplain -dir $path -types {d} -tail .git] ;#won't return hidden :/
return [expr {[llength [list {*}$hiddengitdir {*}$nonhiddengitdir]] > 0}]
} else {
#:/
#unix returns 'hidden' files even without the hidden type being specified - but only if the pattern explicitly matches
return [expr {[llength [glob -nocomplain -dir $path -types d -tail .git]] > 0}] ;#will return .git even though it is conventionally 'hidden' on unix :/
}
} }
proc is_repo_root {{path {}}} { proc is_repo_root {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
expr {[is_fossil_root $path] || [is_git_root $path]} #expr {[is_fossil_root $path] || [is_git_root $path]}
expr {[is_git_root $path] || [is_fossil_root $path]} ;#is_git_root has less to check
} }
#require a minimum of src and src/modules|src/scriptapps|src/*/*.vfs - and that it's otherwise sensible
#we still run a high chance of picking up unintended candidates - but hopefully it's a reasonable balance. #after excluding undesirables;
#require a minimum of
# - (src and src/modules|src/scriptapps|src/vfs)
# - OR (src and punkproject.toml)
# - and that it's otherwise sensible
#we still run a chance of picking up unintended candidates - but hopefully it's a reasonable balance.
proc is_candidate_root {{path {}}} { proc is_candidate_root {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
if {[file pathtype $path] eq "relative"} { if {[file pathtype $path] eq "relative"} {
@ -380,24 +429,34 @@ namespace eval punk::repo {
} }
#review - adjust to allow symlinks to folders? #review - adjust to allow symlinks to folders?
foreach required { #foreach required {
src # src
} { #} {
set req $path/$required # set req $path/$required
if {(![file exists $req]) || ([file type $req] ne "directory") } {return 0} # if {(![file exists $req]) || ([file type $req] ne "directory") } {return 0}
#}
set required [list src]
set found_required [glob -nocomplain -dir $path -types d -tails {*}$required]
if {[llength $found_required] < [llength $required]} {
return 0
} }
set src_subs [glob -nocomplain -dir $path/src -types d -tail *] set src_subs [glob -nocomplain -dir $path/src -types d -tail *]
#test for $path/src/lib is too common to be a useful indicator #test for $path/src/lib is too common to be a useful indicator
if {"modules" in $src_subs || "scriptapps" in $src_subs} { if {"modules" in $src_subs || "vfs" in $src_subs || "scriptapps" in $src_subs} {
#bare minimum 1
return 1 return 1
} }
foreach sub $src_subs {
if {[string match *.vfs $sub]} { #bare minimum2
return 1 # - has src folder and (possibly empty?) punkproject.toml
} if {[file exists $path/punkproject.toml]} {
return 1
} }
#review - do we need to check if path is already within a project?
#can we have a nested project? Seems like asking for complexity and problems when considering possible effects for git/fossil
#todo - breadth first search with depth limit (say depth 3?) for *.tm or *.tcl as another positive qualifier for this dir to be a project-root #todo - breadth first search with depth limit (say depth 3?) for *.tm or *.tcl as another positive qualifier for this dir to be a project-root
#we probably don't want to deep search a src folder in case the user is accidentally in some other type of project's tree #we probably don't want to deep search a src folder in case the user is accidentally in some other type of project's tree
#such a src tree could be very large, so if we don't find tcl indicators near the root it's a good bet this isn't a candidate #such a src tree could be very large, so if we don't find tcl indicators near the root it's a good bet this isn't a candidate
@ -415,14 +474,22 @@ namespace eval punk::repo {
} }
proc is_project_root {path} { proc is_project_root {path} {
#review - find a reliable simple mechanism. Noting we have projects based on different templates. #review - find a reliable simple mechanism. Noting we have projects based on different templates.
#Should there be a specific required 'project' file of some sort? #Should there be a specific required 'project' file of some sort?
#(punkproject.toml is a candidate)
#we don't want to solely rely on such a file being present
# - we may also have punkproject.toml in project_layout template folders for example
#test for file/folder items indicating fossil or git workdir base #test for file/folder items indicating fossil or git workdir base
if {(![punk::repo::is_fossil_root $path]) && (![punk::repo::is_git_root $path])} { #the 'dev' mechanism for creating projects automatically creates a fossil project
#(which can be ignored if the user wants to manage it with git - but should probably remain in place? review)
#however - we currently require that for it to be a 'project' there must be some version control.
#REVIEW.
#
if {![punk::repo::is_repo_root $path]} {
return 0 return 0
} }
#exclude some known places we wouldn't want to put a project #exclude some known places we wouldn't want to put a project
if {![is_candidate_root $path]} { if {![is_candidate_root $path]} {
return 0 return 0
} }
@ -456,7 +523,7 @@ namespace eval punk::repo {
if {$abspath in [dict keys $defaults]} { if {$abspath in [dict keys $defaults]} {
set args [list $abspath {*}$args] set args [list $abspath {*}$args]
set abspath "" set abspath ""
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
@ -793,7 +860,7 @@ namespace eval punk::repo {
} }
} }
if {$repotype eq "git"} { if {$repotype eq "git"} {
dict set fieldnames extra "extra (files/folders)" dict set fieldnames extra "extra (files/folders)"
} }
set col1_fields [list] set col1_fields [list]
set col2_values [list] set col2_values [list]
@ -846,6 +913,7 @@ namespace eval punk::repo {
#determine nature of possibly-nested repositories (of various types) at and above this path #determine nature of possibly-nested repositories (of various types) at and above this path
#Treat an untracked 'candidate' folder as a sort of repository #Treat an untracked 'candidate' folder as a sort of repository
proc find_repos {path} { proc find_repos {path} {
puts "find_repos '$path'"
set start_dir $path set start_dir $path
#root is a 'project' if it it meets the candidate requrements and is under repo control #root is a 'project' if it it meets the candidate requrements and is under repo control
@ -860,6 +928,10 @@ namespace eval punk::repo {
while {[string length [set fosroot [punk::repo::find_fossil $fos_search_from]]]} { while {[string length [set fosroot [punk::repo::find_fossil $fos_search_from]]]} {
lappend fossils_bottom_to_top $fosroot lappend fossils_bottom_to_top $fosroot
set fos_search_from [file dirname $fosroot] set fos_search_from [file dirname $fosroot]
if {$fos_search_from eq $fosroot} {
#root of filesystem is repo - unusual case - but without this we would never escape the while loop
break
}
} }
dict set root_dict fossil $fossils_bottom_to_top dict set root_dict fossil $fossils_bottom_to_top
@ -868,6 +940,9 @@ namespace eval punk::repo {
while {[string length [set gitroot [punk::repo::find_git $git_search_from]]]} { while {[string length [set gitroot [punk::repo::find_git $git_search_from]]]} {
lappend gits_bottom_to_top $gitroot lappend gits_bottom_to_top $gitroot
set git_search_from [file dirname $gitroot] set git_search_from [file dirname $gitroot]
if {$git_search_from eq $gitroot} {
break
}
} }
dict set root_dict git $gits_bottom_to_top dict set root_dict git $gits_bottom_to_top
@ -876,6 +951,9 @@ namespace eval punk::repo {
while {[string length [set candroot [punk::repo::find_candidate $cand_search_from]]]} { while {[string length [set candroot [punk::repo::find_candidate $cand_search_from]]]} {
lappend candidates_bottom_to_top $candroot lappend candidates_bottom_to_top $candroot
set cand_search_from [file dirname $candroot] set cand_search_from [file dirname $candroot]
if {$cand_search_from eq $candroot} {
break
}
} }
dict set root_dict candidate $candidates_bottom_to_top dict set root_dict candidate $candidates_bottom_to_top
@ -936,14 +1014,14 @@ namespace eval punk::repo {
dict set root_dict closest [lindex $longest_first 0 1] ;#the *path* of the closest to start_dir dict set root_dict closest [lindex $longest_first 0 1] ;#the *path* of the closest to start_dir
dict set root_dict closest_types [lindex $longest_first 0 0] dict set root_dict closest_types [lindex $longest_first 0 0]
} }
set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_fossil_len [llength [file split $closest_fossil]] set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_git [lindex [dict get $root_dict git] 0] set closest_fossil_len [llength [file split $closest_fossil]]
set closest_git_len [llength [file split $closest_git]] set closest_git [lindex [dict get $root_dict git] 0]
set closest_candidate [lindex [dict get $root_dict candidate] 0] set closest_git_len [llength [file split $closest_git]]
set closest_candidate_len [llength [file split $closest_candidate]] set closest_candidate [lindex [dict get $root_dict candidate] 0]
set closest_candidate_len [llength [file split $closest_candidate]]
if {$closest_candidate_len > $closest_fossil_len && $closest_candidate_len > $closest_git_len} { if {$closest_candidate_len > $closest_fossil_len && $closest_candidate_len > $closest_git_len} {
#only warn if this candidate is *within* a found repo root #only warn if this candidate is *within* a found repo root
@ -1079,7 +1157,7 @@ namespace eval punk::repo {
} }
if {$opt_ansi} { if {$opt_ansi} {
if {$opt_ansi_prompt eq "\uFFFF"} { if {$opt_ansi_prompt eq "\uFFFF"} {
set ansiprompt [a+ green bold] set ansiprompt [a+ green bold]
} else { } else {
set ansiprompt [$opt_ansi_prompt] set ansiprompt [$opt_ansi_prompt]
} }
@ -1112,15 +1190,15 @@ namespace eval punk::repo {
#Whilst it might detect a central repo folder in a non-standard location - it might also be annoying. #Whilst it might detect a central repo folder in a non-standard location - it might also be annoying.
#Todo - a separate environment variable for users to declare one or more locations where they would like to store project .fossil repositories? #Todo - a separate environment variable for users to declare one or more locations where they would like to store project .fossil repositories?
set candidate_repo_folder_locations [list] set candidate_repo_folder_locations [list]
#- choose a sensible default based on where fossil put the global config dir - or on the existence of a .fossils folder in a 'standard' location #- choose a sensible default based on where fossil put the global config dir - or on the existence of a .fossils folder in a 'standard' location
#verify with user before creating a .fossils folder #verify with user before creating a .fossils folder
#always check env(FOSSIL_HOME) first - but this is designed to locate the global .fossil (or _fossil) file - .fossils repository folder doesn't have to be at the same location #always check env(FOSSIL_HOME) first - but this is designed to locate the global .fossil (or _fossil) file - .fossils repository folder doesn't have to be at the same location
set usable_repo_folder_locations [list] set usable_repo_folder_locations [list]
#If we find one, but it's not writable - add it to another list #If we find one, but it's not writable - add it to another list
set readonly_repo_folder_locations [list] set readonly_repo_folder_locations [list]
#Examine a few possible locations for .fossils folder set #Examine a few possible locations for .fossils folder set
#if containing folder is writable add to candidate list #if containing folder is writable add to candidate list
set testpaths [list] set testpaths [list]
@ -1129,8 +1207,8 @@ namespace eval punk::repo {
if {![catch {package require Tcl 8.7-}]} { if {![catch {package require Tcl 8.7-}]} {
set fossilhome [file normalize [file tildeexpand $fossilhome_raw]] set fossilhome [file normalize [file tildeexpand $fossilhome_raw]]
} else { } else {
#8.6 #8.6
set fossilhome [file normalize $fossilhome_raw] set fossilhome [file normalize $fossilhome_raw]
} }
lappend testpaths [file join $fossilhome .fossils] lappend testpaths [file join $fossilhome .fossils]
@ -1175,13 +1253,13 @@ namespace eval punk::repo {
} }
} }
} }
set startdir_fossils [glob -nocomplain -dir $startdir -type f *.fossil] set startdir_fossils [glob -nocomplain -dir $startdir -type f *.fossil]
if {[llength $startdir_fossils]} { if {[llength $startdir_fossils]} {
#user is already keeping .fossil files directly in curent dir - give them the option to easily keep doing this #user is already keeping .fossil files directly in curent dir - give them the option to easily keep doing this
#(we don't add it if no .fossil files there already - as it is probably a niche requirement - or a sign the user hasn't thought about a better/central location) #(we don't add it if no .fossil files there already - as it is probably a niche requirement - or a sign the user hasn't thought about a better/central location)
if {$startdir ni $usable_repo_folder_locations} { if {$startdir ni $usable_repo_folder_locations} {
lappend usable_repo_folder_locations $startdir lappend usable_repo_folder_locations $startdir
} }
} }
set choice_folders [list] set choice_folders [list]
@ -1207,7 +1285,7 @@ namespace eval punk::repo {
#no existing writable .fossil folders (and no existing .fossil files in startdir) #no existing writable .fossil folders (and no existing .fossil files in startdir)
#offer the (writable) candidate_repo_folder_locations #offer the (writable) candidate_repo_folder_locations
foreach fld $candidate_repo_folder_locations { foreach fld $candidate_repo_folder_locations {
lappend choice_folders [list index $i folder $fld folderexists 0 existingfossils "" conflict ""] lappend choice_folders [list index $i folder $fld folderexists 0 existingfossils "" conflict ""]
incr i incr i
} }
} }
@ -1230,7 +1308,7 @@ namespace eval punk::repo {
} }
set folderexists [dict get $option folderexists] set folderexists [dict get $option folderexists]
if {$folderexists} { if {$folderexists} {
set folderstatus "(existing folder)" set folderstatus "(existing folder)"
} else { } else {
set folderstatus "(CREATE folder for .fossil repository files)" set folderstatus "(CREATE folder for .fossil repository files)"
} }
@ -1238,7 +1316,7 @@ namespace eval punk::repo {
} }
#append the readonly_repo_folder_locations so that user is aware of them as it may affect their choice #append the readonly_repo_folder_locations so that user is aware of them as it may affect their choice
if {[llength $readonly_repo_folder_locations]} { if {[llength $readonly_repo_folder_locations]} {
append menu_message "--------------------------------------------------" \n append menu_message "--------------------------------------------------" \n
foreach readonly $readonly_repo_folder_locations { foreach readonly $readonly_repo_folder_locations {
@ -1256,11 +1334,11 @@ namespace eval punk::repo {
} else { } else {
if {[llength $choice_folders] || $opt_askpath} { if {[llength $choice_folders] || $opt_askpath} {
puts stdout $menu_message puts stdout $menu_message
set max [llength $choice_folders] set max [llength $choice_folders]
if {$max == 1} { if {$max == 1} {
set rangemsg "the number 1" set rangemsg "the number 1"
} else { } else {
set rangemsg "a number from 1 to $max" set rangemsg "a number from 1 to $max"
} }
set menuprompt "${ansiprompt}Enter $rangemsg to select location. (or N to abort)${ansireset}" set menuprompt "${ansiprompt}Enter $rangemsg to select location. (or N to abort)${ansireset}"
if {$opt_askpath} { if {$opt_askpath} {
@ -1279,7 +1357,7 @@ namespace eval punk::repo {
set answer [askuser "${ansiprompt}Do you want to create this folder? Type just the word mkdir to create it, or N for no${ansireset}"] set answer [askuser "${ansiprompt}Do you want to create this folder? Type just the word mkdir to create it, or N for no${ansireset}"]
if {[string equal mkdir [string tolower $answer]]} { if {[string equal mkdir [string tolower $answer]]} {
if {[catch {file mkdir $repository_folder} errM]} { if {[catch {file mkdir $repository_folder} errM]} {
puts stderr "Failed to create folder $repository_folder. Error $errM" puts stderr "Failed to create folder $repository_folder. Error $errM"
} }
} }
} else { } else {
@ -1317,7 +1395,7 @@ namespace eval punk::repo {
if {$index >= 0 && $index <= $max-1} { if {$index >= 0 && $index <= $max-1} {
set repo_folder_choice [lindex $choice_folders $index] set repo_folder_choice [lindex $choice_folders $index]
set repository_folder [dict get $repo_folder_choice folder] set repository_folder [dict get $repo_folder_choice folder]
puts stdout "Selected fossil location $repository_folder" puts stdout "Selected fossil location $repository_folder"
} else { } else {
puts stderr " No menu number matched - aborting." puts stderr " No menu number matched - aborting."
return return
@ -1367,7 +1445,7 @@ namespace eval punk::repo {
proc fossil_revision {{path {}}} { proc fossil_revision {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
# ::kettle::path::revision.fossil # ::kettle::path::revision.fossil
set fossilcmd [Cached_auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
@ -1381,7 +1459,7 @@ namespace eval punk::repo {
proc fossil_remote {{path {}}} { proc fossil_remote {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
# ::kettle::path::revision.fossil # ::kettle::path::revision.fossil
set fossilcmd [Cached_auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
@ -1395,11 +1473,11 @@ namespace eval punk::repo {
proc fossil_get_configdb {{path {}}} { proc fossil_get_configdb {{path {}}} {
#fossil info will *usually* give us the necessary config-db info whether in a project folder or not but.. #fossil info will *usually* give us the necessary config-db info whether in a project folder or not but..
#a) It's expensive to shell-out and call it #a) It's expensive to shell-out and call it
#b) it won't give us a result if we are in a checkout folder which has had its repository moved #b) it won't give us a result if we are in a checkout folder which has had its repository moved
#this fairly extensive mechanism is designed to find it even if the environment has some weird goings-on regarding the filesystem/environment variables #this fairly extensive mechanism is designed to find it even if the environment has some weird goings-on regarding the filesystem/environment variables
#This is unlikely to be necessary in most scenarios, where the location is related to the user's home directory #This is unlikely to be necessary in most scenarios, where the location is related to the user's home directory
#attempt 1 - environment vars and well-known locations #attempt 1 - environment vars and well-known locations
#This is first because it's faster - but hopefully it's aligned with how fossil does it #This is first because it's faster - but hopefully it's aligned with how fossil does it
if {"windows" eq $::tcl_platform(platform)} { if {"windows" eq $::tcl_platform(platform)} {
@ -1416,7 +1494,7 @@ namespace eval punk::repo {
if {[file exists $testfile]} { if {[file exists $testfile]} {
return $testfile return $testfile
} }
} }
} else { } else {
foreach varname [list FOSSIL_HOME HOME ] { foreach varname [list FOSSIL_HOME HOME ] {
if {[info exists ::env($varname)]} { if {[info exists ::env($varname)]} {
@ -1435,13 +1513,13 @@ namespace eval punk::repo {
if {[file exists $testfile]} { if {[file exists $testfile]} {
return $testfile return $testfile
} }
} }
if {[info exists ::env(HOME)]} { if {[info exists ::env(HOME)]} {
set testfile [file join $::env(HOME) .config fossil.db] set testfile [file join $::env(HOME) .config fossil.db]
if {[file exists $testfile]} { if {[file exists $testfile]} {
return $testfile return $testfile
} }
} }
} }
@ -1484,13 +1562,13 @@ namespace eval punk::repo {
cd $original_cwd cd $original_cwd
} }
#attempt 3 - getting desperate.. find other repos, determine their checkouts and run fossil in them to get a result #attempt 3 - getting desperate.. find other repos, determine their checkouts and run fossil in them to get a result
if {$fossil_ok} { if {$fossil_ok} {
#It should be extremely rare to need to resort to sqlite on the databases to find other potential repo paths #It should be extremely rare to need to resort to sqlite on the databases to find other potential repo paths
#Conceivably only on some weird VFS or where some other filesystem strangeness is going on with our original path - or if the root volume itself is a broken fossil checkout #Conceivably only on some weird VFS or where some other filesystem strangeness is going on with our original path - or if the root volume itself is a broken fossil checkout
#Examining the other repos gives us a chance at discovering some other filesystem/paths where things may not be broken #Examining the other repos gives us a chance at discovering some other filesystem/paths where things may not be broken
if {![catch {package require sqlite3} errPackage]} { if {![catch {package require sqlite3} errPackage]} {
#use fossil all ls and sqlite #use fossil all ls and sqlite
if {[catch {exec {*}$fossilcmd all ls} repolines]} { if {[catch {exec {*}$fossilcmd all ls} repolines]} {
error "fossil_get_configdb cannot find repositories" error "fossil_get_configdb cannot find repositories"
} else { } else {
@ -1535,7 +1613,7 @@ namespace eval punk::repo {
error "fossil_get_configdb exhausted search options" error "fossil_get_configdb exhausted search options"
} }
#------------------------------------ #------------------------------------
#temporarily cd to workpath to run script - return to correct path even on failure #temporarily cd to workpath to run script - return to correct path even on failure
proc do_in_path {path script} { proc do_in_path {path script} {
#from ::kettle::path::in #from ::kettle::path::in
@ -1611,8 +1689,8 @@ namespace eval punk::repo {
set platform $::tcl_platform(platform) set platform $::tcl_platform(platform)
} }
#No - don't do this sort of path translation here - leave as option for specific utils only such as ./ #No - don't do this sort of path translation here - leave as option for specific utils only such as ./
#Windows volume-relative syntax with specific volume specified is somewhat broken in Tcl - but leading slash volume-relative does work #Windows volume-relative syntax with specific volume specified is somewhat broken in Tcl - but leading slash volume-relative does work
#We shouldn't break it totally just because accessing WSL/mingw paths is slightly more useful #We shouldn't break it totally just because accessing WSL/mingw paths is slightly more useful
#if {$platform eq "windows"} { #if {$platform eq "windows"} {
#return [file dirname [file normalize [punk::unixywindows::towinpath $path]/__]] #return [file dirname [file normalize [punk::unixywindows::towinpath $path]/__]]
@ -1624,7 +1702,7 @@ namespace eval punk::repo {
#This taken from kettle::path::strip #This taken from kettle::path::strip
#It doesn't compare the prefix contents presumably for speed when used in kettle::path::scan #It doesn't compare the prefix contents presumably for speed when used in kettle::path::scan
#renamed to better indicate its behaviour #renamed to better indicate its behaviour
proc path_strip_prefixdepth {path prefix} { proc path_strip_prefixdepth {path prefix} {
if {$prefix eq ""} { if {$prefix eq ""} {
return [norm $path] return [norm $path]
@ -1713,9 +1791,9 @@ namespace eval ::punk::args::register {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::repo [namespace eval punk::repo { package provide punk::repo [namespace eval punk::repo {
variable version variable version
set version 0.1.1 set version 0.1.1
}] }]
return return

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

@ -1,239 +1,239 @@
#utilities for punk apps to call #utilities for punk apps to call
package provide punkapp [namespace eval punkapp { package provide punkapp [namespace eval punkapp {
variable version variable version
set version 0.1 set version 0.1
}] }]
namespace eval punkapp { namespace eval punkapp {
variable result variable result
variable waiting "no" variable waiting "no"
proc hide_dot_window {} { proc hide_dot_window {} {
#alternative to wm withdraw . #alternative to wm withdraw .
#see https://wiki.tcl-lang.org/page/wm+withdraw #see https://wiki.tcl-lang.org/page/wm+withdraw
wm geometry . 1x1+0+0 wm geometry . 1x1+0+0
wm overrideredirect . 1 wm overrideredirect . 1
wm transient . wm transient .
} }
proc is_toplevel {w} { proc is_toplevel {w} {
if {![llength [info commands winfo]]} { if {![llength [info commands winfo]]} {
return 0 return 0
} }
expr {[winfo toplevel $w] eq $w && ![catch {$w cget -menu}]} expr {[winfo toplevel $w] eq $w && ![catch {$w cget -menu}]}
} }
proc get_toplevels {{w .}} { proc get_toplevels {{w .}} {
if {![llength [info commands winfo]]} { if {![llength [info commands winfo]]} {
return [list] return [list]
} }
set list {} set list {}
if {[is_toplevel $w]} { if {[is_toplevel $w]} {
lappend list $w lappend list $w
} }
foreach w [winfo children $w] { foreach w [winfo children $w] {
lappend list {*}[get_toplevels $w] lappend list {*}[get_toplevels $w]
} }
return $list return $list
} }
proc make_toplevel_next {prefix} { proc make_toplevel_next {prefix} {
set top [get_toplevel_next $prefix] set top [get_toplevel_next $prefix]
return [toplevel $top] return [toplevel $top]
} }
#possible race condition if multiple calls made without actually creating the toplevel, or gap if highest existing closed in the meantime #possible race condition if multiple calls made without actually creating the toplevel, or gap if highest existing closed in the meantime
#todo - reserve_toplevel_next ? keep list of toplevels considered 'allocated' even if never created or already destroyed? what usecase? #todo - reserve_toplevel_next ? keep list of toplevels considered 'allocated' even if never created or already destroyed? what usecase?
#can call wm withdraw to to reserve newly created toplevel. To stop re-use of existing names after destruction would require a list or at least a record of highest created for each prefix #can call wm withdraw to to reserve newly created toplevel. To stop re-use of existing names after destruction would require a list or at least a record of highest created for each prefix
proc get_toplevel_next {prefix} { proc get_toplevel_next {prefix} {
set base [string trim $prefix .] ;# .myapp -> myapp .myapp.somewindow -> myapp.somewindow . -> "" set base [string trim $prefix .] ;# .myapp -> myapp .myapp.somewindow -> myapp.somewindow . -> ""
} }
proc exit {{toplevel ""}} { proc exit {{toplevel ""}} {
variable waiting variable waiting
variable result variable result
variable default_result variable default_result
set toplevels [get_toplevels] set toplevels [get_toplevels]
if {[string length $toplevel]} { if {[string length $toplevel]} {
set wposn [lsearch $toplevels $toplevel] set wposn [lsearch $toplevels $toplevel]
if {$wposn > 0} { if {$wposn > 0} {
destroy $toplevel destroy $toplevel
} }
} else { } else {
#review #review
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} { if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
puts stderr "punkapp::exit called without toplevel - showing console" puts stderr "punkapp::exit called without toplevel - showing console"
show_console show_console
return 0 return 0
} else { } else {
puts stderr "punkapp::exit called without toplevel - exiting" puts stderr "punkapp::exit called without toplevel - exiting"
if {$waiting ne "no"} { if {$waiting ne "no"} {
if {[info exists result(shell)]} { if {[info exists result(shell)]} {
set temp [set result(shell)] set temp [set result(shell)]
unset result(shell) unset result(shell)
set waiting $temp set waiting $temp
} else { } else {
set waiting "" set waiting ""
} }
} else { } else {
::exit ::exit
} }
} }
} }
set controllable [get_user_controllable_toplevels] set controllable [get_user_controllable_toplevels]
if {![llength $controllable]} { if {![llength $controllable]} {
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} { if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
show_console show_console
} else { } else {
if {$waiting ne "no"} { if {$waiting ne "no"} {
if {[info exists result(shell)]} { if {[info exists result(shell)]} {
set temp [set result(shell)] set temp [set result(shell)]
unset result(shell) unset result(shell)
set waiting $temp set waiting $temp
} elseif {[info exists result($toplevel)]} { } elseif {[info exists result($toplevel)]} {
set temp [set result($toplevel)] set temp [set result($toplevel)]
unset result($toplevel) unset result($toplevel)
set waiting $temp set waiting $temp
} elseif {[info exists default_result]} { } elseif {[info exists default_result]} {
set temp $default_result set temp $default_result
unset default_result unset default_result
set waiting $temp set waiting $temp
} else { } else {
set waiting "" set waiting ""
} }
} else { } else {
::exit ::exit
} }
} }
} }
} }
proc close_window {toplevel} { proc close_window {toplevel} {
wm withdraw $toplevel wm withdraw $toplevel
if {![llength [get_user_controllable_toplevels]]} { if {![llength [get_user_controllable_toplevels]]} {
punkapp::exit $toplevel punkapp::exit $toplevel
} }
destroy $toplevel destroy $toplevel
} }
proc wait {args} { proc wait {args} {
variable waiting variable waiting
variable default_result variable default_result
if {[dict exists $args -defaultresult]} { if {[dict exists $args -defaultresult]} {
set default_result [dict get $args -defaultresult] set default_result [dict get $args -defaultresult]
} }
foreach t [punkapp::get_toplevels] { foreach t [punkapp::get_toplevels] {
if {[wm protocol $t WM_DELETE_WINDOW] eq ""} { if {[wm protocol $t WM_DELETE_WINDOW] eq ""} {
wm protocol $t WM_DELETE_WINDOW [list punkapp::close_window $t] wm protocol $t WM_DELETE_WINDOW [list punkapp::close_window $t]
} }
} }
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} { if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
puts stderr "repl eventloop seems to be running - punkapp::wait not required" puts stderr "repl eventloop seems to be running - punkapp::wait not required"
} else { } else {
if {$waiting eq "no"} { if {$waiting eq "no"} {
set waiting "waiting" set waiting "waiting"
vwait ::punkapp::waiting vwait ::punkapp::waiting
return $::punkapp::waiting return $::punkapp::waiting
} }
} }
} }
#A window can be 'visible' according to this - but underneath other windows etc #A window can be 'visible' according to this - but underneath other windows etc
#REVIEW - change name? #REVIEW - change name?
proc get_visible_toplevels {{w .}} { proc get_visible_toplevels {{w .}} {
if {![llength [info commands winfo]]} { if {![llength [info commands winfo]]} {
return [list] return [list]
} }
set list [get_toplevels $w] set list [get_toplevels $w]
set mapped [lmap v $list {expr {[winfo ismapped $v] ? $v : {}}}] set mapped [lmap v $list {expr {[winfo ismapped $v] ? $v : {}}}]
set mapped [concat {*}$mapped] ;#ignore {} set mapped [concat {*}$mapped] ;#ignore {}
set visible [list] set visible [list]
foreach m $mapped { foreach m $mapped {
if {[wm overrideredirect $m] == 0 } { if {[wm overrideredirect $m] == 0 } {
lappend visible $m lappend visible $m
} else { } else {
if {[winfo height $m] >1 && [winfo width $m] > 1} { if {[winfo height $m] >1 && [winfo width $m] > 1} {
#technically even a 1x1 is visible.. but in practice even a 10x10 is hardly likely to be noticeable when overrideredirect == 1 #technically even a 1x1 is visible.. but in practice even a 10x10 is hardly likely to be noticeable when overrideredirect == 1
#as a convention - 1x1 with no controls is used to make a window invisible so we'll treat anything larger as visible #as a convention - 1x1 with no controls is used to make a window invisible so we'll treat anything larger as visible
lappend visible $m lappend visible $m
} }
} }
} }
return $visible return $visible
} }
proc get_user_controllable_toplevels {{w .}} { proc get_user_controllable_toplevels {{w .}} {
set visible [get_visible_toplevels $w] set visible [get_visible_toplevels $w]
set controllable [list] set controllable [list]
foreach v $visible { foreach v $visible {
if {[wm overrideredirect $v] == 0} { if {[wm overrideredirect $v] == 0} {
lappend controllable $v lappend controllable $v
} }
} }
#only return visible windows with overrideredirect == 0 because there exists some user control. #only return visible windows with overrideredirect == 0 because there exists some user control.
#todo - review.. consider checking if position is outside screen areas? Technically controllable.. but not easily #todo - review.. consider checking if position is outside screen areas? Technically controllable.. but not easily
return $controllable return $controllable
} }
proc hide_console {args} { proc hide_console {args} {
set opts [dict create -force 0] set opts [dict create -force 0]
if {([llength $args] % 2) != 0} { if {([llength $args] % 2) != 0} {
error "hide_console expects pairs of arguments. e.g -force 1" error "hide_console expects pairs of arguments. e.g -force 1"
} }
#set known_opts [dict keys $defaults] #set known_opts [dict keys $defaults]
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-force { -force {
dict set opts $k $v dict set opts $k $v
} }
default { default {
error "Unrecognised options '$k' known options: [dict keys $opts]" error "Unrecognised options '$k' known options: [dict keys $opts]"
} }
} }
} }
set force [dict get $opts -force] set force [dict get $opts -force]
if {!$force} { if {!$force} {
if {![llength [get_user_controllable_toplevels]]} { if {![llength [get_user_controllable_toplevels]]} {
puts stderr "Cannot hide console while no user-controllable windows available" puts stderr "Cannot hide console while no user-controllable windows available"
return 0 return 0
} }
} }
if {$::tcl_platform(platform) eq "windows"} { if {$::tcl_platform(platform) eq "windows"} {
#hide won't work for certain consoles cush as conemu,wezterm - and doesn't really make sense for tabbed windows anyway. #hide won't work for certain consoles cush as conemu,wezterm - and doesn't really make sense for tabbed windows anyway.
#It would be nice if we could tell the console window to hide just the relevant tab - or the whole window if only one tab present - but this is unlikely to be possible in any standard way. #It would be nice if we could tell the console window to hide just the relevant tab - or the whole window if only one tab present - but this is unlikely to be possible in any standard way.
#an ordinary cmd.exe or pwsh.exe or powershell.exe window can be hidden ok though. #an ordinary cmd.exe or pwsh.exe or powershell.exe window can be hidden ok though.
#(but with wezterm - process is cmd.exe - but it has style popup and can't be hidden with a twapi::hide_window call) #(but with wezterm - process is cmd.exe - but it has style popup and can't be hidden with a twapi::hide_window call)
package require twapi package require twapi
set h [twapi::get_console_window] set h [twapi::get_console_window]
set pid [twapi::get_window_process $h] set pid [twapi::get_window_process $h]
set pinfo [twapi::get_process_info $pid -name] set pinfo [twapi::get_process_info $pid -name]
set pname [dict get $pinfo -name] set pname [dict get $pinfo -name]
set wstyle [twapi::get_window_style $h] set wstyle [twapi::get_window_style $h]
#tclkitsh/tclsh? #tclkitsh/tclsh?
if {($pname in [list cmd.exe pwsh.exe powershell.exe] || [string match punk*.exe $pname]) && "popup" ni $wstyle} { if {($pname in [list cmd.exe pwsh.exe powershell.exe] || [string match punk*.exe $pname]) && "popup" ni $wstyle} {
twapi::hide_window $h twapi::hide_window $h
return 1 return 1
} else { } else {
puts stderr "punkapp::hide_console unable to hide this type of console window" puts stderr "punkapp::hide_console unable to hide this type of console window"
return 0 return 0
} }
} else { } else {
#todo #todo
puts stderr "punkapp::hide_console unimplemented on this platform (todo)" puts stderr "punkapp::hide_console unimplemented on this platform (todo)"
return 0 return 0
} }
} }
proc show_console {} { proc show_console {} {
if {$::tcl_platform(platform) eq "windows"} { if {$::tcl_platform(platform) eq "windows"} {
package require twapi package require twapi
if {![catch {set h [twapi::get_console_window]} errM]} { if {![catch {set h [twapi::get_console_window]} errM]} {
twapi::show_window $h -activate -normal twapi::show_window $h -activate -normal
} else { } else {
#no console - assume launched from something like wish? #no console - assume launched from something like wish?
catch {console show} catch {console show}
} }
} else { } else {
#todo #todo
puts stderr "punkapp::show_console unimplemented on this platform" puts stderr "punkapp::show_console unimplemented on this platform"
} }
} }
} }

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

@ -243,12 +243,14 @@ namespace eval punkcheck {
} }
method get_targets_exist {} { method get_targets_exist {} {
set punkcheck_folder [file dirname [$o_installer get_checkfile]] set punkcheck_folder [file dirname [$o_installer get_checkfile]]
set existing [list] set existing [glob -nocomplain -dir $punkcheck_folder -tails {*}$o_targets]
foreach t $o_targets {
if {[file exists [file join $punkcheck_folder $t]]} { #set existing [list]
lappend existing $t #foreach t $o_targets {
} # if {[file exists [file join $punkcheck_folder $t]]} {
} # lappend existing $t
# }
#}
return $existing return $existing
} }
method end {} { method end {} {
@ -880,19 +882,46 @@ namespace eval punkcheck {
#allow nonexistant as a source #allow nonexistant as a source
set fpath [file join $punkcheck_folder $source_relpath] set fpath [file join $punkcheck_folder $source_relpath]
if {![file exists $fpath]} { #windows: file exist + file type = 2ms vs 500ms for 2x glob
set floc [file dirname $fpath]
set fname [file tail $fpath]
set file_set [glob -nocomplain -dir $floc -type f -tails $fname]
set dir_set [glob -nocomplain -dir $floc -type d -tails $fname]
set link_set [glob -nocomplain -dir $floc -type l -tails $fname]
if {[llength $file_set] == 0 && [llength $dir_set] == 0 && [llength $link_set] == 0} {
#could also theoretically exist as less common types, b,c,p,s (block,char,pipe,socket)
#- we don't expect them here - REVIEW - ever possible?
#- installing/examining such things an unlikely usecase and would require special handling anyway.
set ftype "missing" set ftype "missing"
set fsize "" set fsize ""
} else { } else {
set ftype [file type $fpath] if {[llength $dir_set]} {
if {$ftype eq "directory"} { set ftype "directory"
set fsize "NA" set fsize "NA"
} elseif {[llength $link_set]} {
set ftype "link"
set fsize 0
} else { } else {
set ftype "file"
#todo - optionally use mtime instead of cksum (for files only)? #todo - optionally use mtime instead of cksum (for files only)?
#mtime is not reliable across platforms and filesystems though.. see article linked at top. #mtime is not reliable across platforms and filesystems though.. see article linked at top.
set fsize [file size $fpath] set fsize [file size $fpath]
} }
} }
#if {![file exists $fpath]} {
# set ftype "missing"
# set fsize ""
#} else {
# set ftype [file type $fpath]
# if {$ftype eq "directory"} {
# set fsize "NA"
# } else {
# #todo - optionally use mtime instead of cksum (for files only)?
# #mtime is not reliable across platforms and filesystems though.. see article linked at top.
# set fsize [file size $fpath]
# }
#}
#get_relativecksum_from_base and fill_relativecksums_from_base_and_relativepathdict will set cksum to <PATHNOTFOUND> if fpath doesn't exist #get_relativecksum_from_base and fill_relativecksums_from_base_and_relativepathdict will set cksum to <PATHNOTFOUND> if fpath doesn't exist
if {$use_cache} { if {$use_cache} {
set source_cksum_info [punk::mix::base::lib::fill_relativecksums_from_base_and_relativepathdict $punkcheck_folder [dict create $source_relpath $use_cache_record]] set source_cksum_info [punk::mix::base::lib::fill_relativecksums_from_base_and_relativepathdict $punkcheck_folder [dict create $source_relpath $use_cache_record]]
@ -1648,6 +1677,10 @@ namespace eval punkcheck {
set is_skip 0 set is_skip 0
if {$overwrite_what eq "all-targets"} { if {$overwrite_what eq "all-targets"} {
file mkdir $current_target_dir file mkdir $current_target_dir
#--------------------------------------------
#sometimes we get the error: 'error copying "file1" to "file2": invalid argument'
#--------------------------------------------
puts stderr "punkcheck: about to: file copy -force $current_source_dir/$m $current_target_dir"
file copy -force $current_source_dir/$m $current_target_dir file copy -force $current_source_dir/$m $current_target_dir
lappend files_copied $current_source_dir/$m lappend files_copied $current_source_dir/$m
} else { } else {
@ -1859,22 +1892,75 @@ namespace eval punkcheck {
return [list files_copied $files_copied files_skipped $files_skipped sources_unchanged $sources_unchanged antiglob_paths_matched $antiglob_paths_matched punkcheck_records $punkcheck_records punkcheck_folder $punkcheck_folder srcdir $srcdir tgtdir $tgtdir] return [list files_copied $files_copied files_skipped $files_skipped sources_unchanged $sources_unchanged antiglob_paths_matched $antiglob_paths_matched punkcheck_records $punkcheck_records punkcheck_folder $punkcheck_folder srcdir $srcdir tgtdir $tgtdir]
} }
proc summarize_install_resultdict {resultdict} {
lappend PUNKARGS [list {
@id -id ::punkcheck::summarize_install_resultdict
@cmd -name punkcheck::summarize_install_resultdict -help\
"Emits a string summarizing a punkcheck resultdict, showing
how many items were copied, and the source, target locations"
@opts
-title -type string -default ""
-forcecolour -type boolean -default 0 -help\
"When true, passes the forcecolour tag to punk::ansi functions.
This enables ANSI sgr colours even when colour
is off. (ignoring env(NO_COLOR))
To disable colour - ensure the NO_COLOR env var is set,
or use:
namespace eval ::punk::console {variable colour_disabled 1}"
@values -min 1 -max 1
resultdict -type dict
}]
proc summarize_install_resultdict {args} {
set argd [punk::args::parse $args withid ::punkcheck::summarize_install_resultdict]
lassign [dict values $argd] leaders opts values received
set title [dict get $opts -title]
set forcecolour [dict get $opts -forcecolour]
set resultdict [dict get $values resultdict]
set has_ansi [expr {![catch {package require punk::ansi}]}]
if {$has_ansi} {
if {$forcecolour} {
set fc "forcecolour"
} else {
set fc ""
}
set R [punk::ansi::a] ;#reset
set LINE_COLOUR [punk::ansi::a+ {*}$forcecolour bold cyan]
set LOW_COLOUR [punk::ansi::a+ {*}$forcecolour bold green]
set HIGH_COLOUR [punk::ansi::a+ {*}$forcecolour bold yellow]
} else {
set R ""
set LINE_COLOUR ""
set LOW_COLOUR ""
set HIGH_COLOUR ""
}
set msg "" set msg ""
if {[dict size $resultdict]} { if {[dict size $resultdict]} {
set copied [dict get $resultdict files_copied] set copied [dict get $resultdict files_copied]
append msg "--------------------------" \n if {[llength $copied] == 0} {
append msg "[dict keys $resultdict]" \n set HIGHLIGHT $LOW_COLOUR
} else {
set HIGHLIGHT $HIGH_COLOUR
}
set ruler $LINE_COLOUR[string repeat - 78]$R
if {$title ne ""} {
append msg $ruler \n
append msg $title \n
}
append msg $ruler \n
#append msg "[dict keys $resultdict]" \n
set tgtdir [dict get $resultdict tgtdir] set tgtdir [dict get $resultdict tgtdir]
set checkfolder [dict get $resultdict punkcheck_folder] set checkfolder [dict get $resultdict punkcheck_folder]
append msg "Copied [llength $copied] files from [dict get $resultdict srcdir] to [dict get $resultdict tgtdir]" \n append msg "${HIGHLIGHT}Copied [llength $copied] files from [dict get $resultdict srcdir] to [dict get $resultdict tgtdir]$R" \n
foreach f $copied { foreach f $copied {
append msg "COPIED [punkcheck::lib::path_relative $checkfolder $f]" \n append msg "COPIED [punkcheck::lib::path_relative $checkfolder $f]" \n
append msg " TO $tgtdir" \n append msg " TO $tgtdir" \n
} }
append msg "[llength [dict get $resultdict sources_unchanged]] unchanged source files" \n append msg "[llength [dict get $resultdict sources_unchanged]] unchanged source files" \n
append msg "[llength [dict get $resultdict files_skipped]] skipped files" \n append msg "[llength [dict get $resultdict files_skipped]] skipped files" \n
append msg "--------------------------" \n append msg $ruler \n
} }
return $msg return $msg
} }

BIN
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.1.tm

Binary file not shown.

BIN
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.3.tm

Binary file not shown.

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

File diff suppressed because it is too large Load Diff

160
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/tomlish-1.1.2.tm

@ -185,6 +185,8 @@ namespace eval tomlish {
error "tomlish _get_keyval_value invalid to have type TABLE on rhs of =" error "tomlish _get_keyval_value invalid to have type TABLE on rhs of ="
} }
ITABLE { ITABLE {
#This one should not be returned as a type <tag> value <something> structure!
#
set result [::tomlish::to_dict [list $found_sub]] set result [::tomlish::to_dict [list $found_sub]]
} }
ARRAY { ARRAY {
@ -249,6 +251,7 @@ namespace eval tomlish {
} }
#to_dict is a *basic* programmatic datastructure for accessing the data. #to_dict is a *basic* programmatic datastructure for accessing the data.
# produce a dictionary of keys and values from a tomlish tagged list. # produce a dictionary of keys and values from a tomlish tagged list.
# to_dict is primarily for reading toml data. # to_dict is primarily for reading toml data.
@ -271,8 +274,12 @@ namespace eval tomlish {
# so we can raise an error to satisfy the toml rule: 'You cannot define any key or table more than once. Doing so is invalid' # so we can raise an error to satisfy the toml rule: 'You cannot define any key or table more than once. Doing so is invalid'
#Note that [a] and then [a.b] is ok if there are no subkey conflicts - so we are only tracking complete tablenames here. #Note that [a] and then [a.b] is ok if there are no subkey conflicts - so we are only tracking complete tablenames here.
#we don't error out just because a previous tablename segment has already appeared. #we don't error out just because a previous tablename segment has already appeared.
variable tablenames_seen [list] ##variable tablenames_seen [list]
if {[uplevel 1 [list info exists tablenames_seen]]} {
upvar tablenames_seen tablenames_seen
} else {
set tablenames_seen [list]
}
log::info ">>> processing '$tomlish'<<<" log::info ">>> processing '$tomlish'<<<"
set items $tomlish set items $tomlish
@ -311,9 +318,9 @@ namespace eval tomlish {
} }
DOTTEDKEY { DOTTEDKEY {
log::debug "--> processing $tag: $item" log::debug "--> processing $tag: $item"
set dkey_info [_get_dottedkey_info $item] set dkey_info [_get_dottedkey_info $item]
set dotted_key_hierarchy [dict get $dkey_info keys] set dotted_key_hierarchy [dict get $dkey_info keys]
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw] set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw]
#a.b.c = 1 #a.b.c = 1
#table_key_hierarchy -> a b #table_key_hierarchy -> a b
@ -345,6 +352,9 @@ namespace eval tomlish {
set keyval_dict [_get_keyval_value $item] set keyval_dict [_get_keyval_value $item]
dict set datastructure {*}$pathkeys $leafkey $keyval_dict dict set datastructure {*}$pathkeys $leafkey $keyval_dict
#JMN test 2025
} }
TABLE { TABLE {
set tablename [lindex $item 1] set tablename [lindex $item 1]
@ -386,8 +396,40 @@ namespace eval tomlish {
lappend table_key_hierarchy_raw $rawseg lappend table_key_hierarchy_raw $rawseg
if {[dict exists $datastructure {*}$table_key_hierarchy]} { if {[dict exists $datastructure {*}$table_key_hierarchy]} {
#It's ok for this key to already exist *if* it was defined by a previous tablename, #It's ok for this key to already exist *if* it was defined by a previous tablename or equivalent
# but not if it was defined as a key/qkey/skey ? #and if this key is longer
#consider the following 2 which are legal:
#[table]
#x.y = 3
#[table.x.z]
#k= 22
#equivalent
#[table]
#[table.x]
#y = 3
#[table.x.z]
#k=22
#illegal
#[table]
#x.y = 3
#[table.x.y.z]
#k = 22
## - we should bfail on encoungerint table.x.y because only table and table.x are effectively tables
## - we should also fail if
#illegal
#[table]
#x.y = {p=3}
#[table.x.y.z]
#k = 22
## we should fail because y is an inline table which is closed to further entries
#TODO! fix - this code is wrong
set testkey [join $table_key_hierarchy_raw .] set testkey [join $table_key_hierarchy_raw .]
@ -422,7 +464,7 @@ namespace eval tomlish {
if {$found_testkey == 0} { if {$found_testkey == 0} {
#the raw table_key_hierarchy is better to display in the error message, although it's not the actual dict keyset #the raw table_key_hierarchy is better to display in the error message, although it's not the actual dict keyset
set msg "key [join $table_key_hierarchy_raw .] already exists in datastructure, but wasn't defined by a supertable." set msg "key [join $table_key_hierarchy_raw .] already exists in datastructure, but wasn't defined by a supertable."
append msg "tablenames_seen:" append msg \n "tablenames_seen:" \n
foreach ts $tablenames_seen { foreach ts $tablenames_seen {
append msg " " $ts \n append msg " " $ts \n
} }
@ -453,13 +495,18 @@ namespace eval tomlish {
#now add the contained elements #now add the contained elements
foreach element [lrange $item 2 end] { foreach element [lrange $item 2 end] {
set type [lindex $element 0] set type [lindex $element 0]
log::debug "--> $type processing contained element $element"
switch -exact -- $type { switch -exact -- $type {
DOTTEDKEY { DOTTEDKEY {
set dkey_info [_get_dottedkey_info $element] set dkey_info [_get_dottedkey_info $element]
set dotted_key_hierarchy [dict get $dkey_info keys] #e.g1 keys {x.y y} keys_raw {'x.y' y}
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw] #e.g2 keys {x.y y} keys_raw {{"x.y"} y}
set leaf_key [lindex $dotted_key_hierarchy end] set dotted_key_hierarchy [dict get $dkey_info keys]
set dkeys [lrange $dotted_key_hierarchy 0 end-1] set dkeys [lrange $dotted_key_hierarchy 0 end-1]
set leaf_key [lindex $dotted_key_hierarchy end]
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw]
set dkeys_raw [lrange $dotted_key_hierarchy_raw 0 end-1]
set leaf_key_raw [lindex $dotted_key_hierarchy_raw end]
#ensure empty keys are still represented in the datastructure #ensure empty keys are still represented in the datastructure
set test_keys $table_keys set test_keys $table_keys
@ -476,7 +523,22 @@ namespace eval tomlish {
error "Duplicate key '$table_keys $dkeys $leaf_key'. The key already exists at this level in the toml data. The toml data is not valid." error "Duplicate key '$table_keys $dkeys $leaf_key'. The key already exists at this level in the toml data. The toml data is not valid."
} }
set keyval_dict [_get_keyval_value $element] set keyval_dict [_get_keyval_value $element]
#keyval_dict is either a {type <tomltag> value <whatever>}
#or the result from parsing an arbitrary dict from an inline table - which could theoretically look the same at the topmost level
#punk::dict::is_tomlish_typeval can distinguish
puts stdout ">>> $keyval_dict"
dict set datastructure {*}$table_keys {*}$dkeys $leaf_key $keyval_dict dict set datastructure {*}$table_keys {*}$dkeys $leaf_key $keyval_dict
#JMN 2025
#tomlish::utils::normalize_key ??
lappend tablenames_seen [join [list {*}$table_key_hierarchy_raw {*}$dkeys_raw] .] ;#????
#if the keyval_dict is not a simple type x value y - then it's an inline table ?
#if so - we should add the path to the leaf_key as a seen table too - as it's not allowed to have more entries added.
if {![tomlish::dict::is_tomlish_typeval $keyval_dict]} {
#the value is either empty or or a dict structure with arbitrary (from-user-data) toplevel keys
# inner structure will contain {type <tag> value <etc>} if all leaves are not empty ITABLES
lappend tablenames_seen [join [list {*}$table_key_hierarchy_raw {*}$dkeys_raw $leaf_key_raw] .]
}
} }
KEY - QKEY - SQKEY { KEY - QKEY - SQKEY {
#obsolete ? #obsolete ?
@ -777,7 +839,7 @@ namespace eval tomlish {
set result [list] set result [list]
set lastparent [lindex $parents end] set lastparent [lindex $parents end]
if {$lastparent in [list "" do_inline]} { if {$lastparent in [list "" do_inline]} {
if {[tomlish::dict::is_tomltype $vinfo]} { if {[tomlish::dict::is_tomlish_typeval $vinfo]} {
set type [dict get $vinfo type] set type [dict get $vinfo type]
#treat ITABLE differently? #treat ITABLE differently?
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo]
@ -811,7 +873,7 @@ namespace eval tomlish {
} else { } else {
set VK_PART [list KEY $vk] set VK_PART [list KEY $vk]
} }
if {[tomlish::dict::is_tomltype $vv]} { if {[tomlish::dict::is_tomlish_typeval $vv]} {
#type x value y #type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv]
set record [list DOTTEDKEY [list $VK_PART {WS { }}] = {WS { }} $sublist] set record [list DOTTEDKEY [list $VK_PART {WS { }}] = {WS { }} $sublist]
@ -877,7 +939,7 @@ namespace eval tomlish {
} }
} else { } else {
#lastparent is not toplevel "" or "do_inline" #lastparent is not toplevel "" or "do_inline"
if {[tomlish::dict::is_tomltype $vinfo]} { if {[tomlish::dict::is_tomlish_typeval $vinfo]} {
#type x value y #type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo]
lappend result {*}$sublist lappend result {*}$sublist
@ -901,7 +963,7 @@ namespace eval tomlish {
} else { } else {
set VK_PART [list KEY $vk] set VK_PART [list KEY $vk]
} }
if {[tomlish::dict::is_tomltype $vv]} { if {[tomlish::dict::is_tomlish_typeval $vv]} {
#type x value y #type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv]
set record [list DOTTEDKEY [list $VK_PART] = $sublist] set record [list DOTTEDKEY [list $VK_PART] = $sublist]
@ -2404,7 +2466,8 @@ namespace eval tomlish::utils {
} ;#RS } ;#RS
#check if str is valid for use as a toml bare key #check if str is valid for use as a toml bare key
proc is_barekey {str} { #Early toml versions? only allowed letters + underscore + dash
proc is_barekey1 {str} {
if {[tcl::string::length $str] == 0} { if {[tcl::string::length $str] == 0} {
return 0 return 0
} else { } else {
@ -2418,6 +2481,52 @@ namespace eval tomlish::utils {
} }
} }
#from toml.abnf in github.com/toml-lang/toml
#unquoted-key = 1*unquoted-key-char
#unquoted-key-char = ALPHA / DIGIT / %x2D / %x5F ; a-z A-Z 0-9 - _
#unquoted-key-char =/ %xB2 / %xB3 / %xB9 / %xBC-BE ; superscript digits, fractions
#unquoted-key-char =/ %xC0-D6 / %xD8-F6 / %xF8-37D ; non-symbol chars in Latin block
#unquoted-key-char =/ %x37F-1FFF ; exclude GREEK QUESTION MARK, which is basically a semi-colon
#unquoted-key-char =/ %x200C-200D / %x203F-2040 ; from General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ
#unquoted-key-char =/ %x2070-218F / %x2460-24FF ; include super-/subscripts, letterlike/numberlike forms, enclosed alphanumerics
#unquoted-key-char =/ %x2C00-2FEF / %x3001-D7FF ; skip arrows, math, box drawing etc, skip 2FF0-3000 ideographic up/down markers and spaces
#unquoted-key-char =/ %x2070-21FF / %x2300-24FF ; skip math operators
#unquoted-key-char =/ %x25A0-268B / %x2690-2757 ; skip box drawing, block elements, and some yin-yang symbols
#unquoted-key-char =/ %x2762-2767 / %x2776-27E5 ; skip some Dingbat punctuation
#unquoted-key-char =/ %x2801-297F ; skip some math brackets and arrows, and braille blank
#unquoted-key-char =/ %x2B00-2FFF / %x3001-D7FF ; skip various math operators and symbols, and ideographic space
#unquoted-key-char =/ %xF900-FDCF / %xFDF0-FFFD ; skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF intended for process-internal use (unicode)
#unquoted-key-char =/ %x10000-EFFFF ; all chars outside BMP range, excluding Private Use planes (F0000-10FFFF)
variable re_barekey
set ranges [list]
lappend ranges {a-zA-Z0-9\_\-}
lappend ranges {\u00B2} {\u00B3} {\u00B9} {\u00BC-\u00BE} ;# superscript digits, fractions
lappend ranges {\u00C0-\u00D6} {\u00D8-\u00F6} {\u00F8-\u037D} ;# non-symbol chars in Latin block
lappend ranges {\u037f-\u1FFF} ;# exclude GREEK QUESTION MARK, which is basically a semi-colon
lappend ranges {\u200C-\u200D} {\u203F-\u2040} ;# from General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ
lappend ranges {\u2070-\u218f} {\u2460-\u24FF} ;# include super-subscripts, letterlike/numberlike forms, enclosed alphanumerics
lappend ranges {\u2C00-\u2FEF} {\u3001-\uD7FF} ;# skip arrows, math, box drawing etc, skip 2FF0-3000 ideographic up/down markers and spaces
lappend ranges {\u2070-\u21FF} {\u2300-\u24FF} ;# skip math operators
lappend ranges {\u25A0-\u268B} {\u2690-\u2757} ;# skip box drawing, block elements, and some yin-yang symbols
lappend ranges {\u2762-\u2767} {\u2776-\u27E5} ;# skip some Dingbat punctuation
lappend ranges {\u2801-\u297F} ;# skip some math brackets and arrows, and braille blank
lappend ranges {\u2B00-\u2FFF} {\u3001-\uD7FF} ;# skip various math operators and symbols, and ideographic space
lappend ranges {\uF900-\uFDCF} {\uFDF0-\uFFFD} ;# skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF intended for process-internal use (unicode)
lappend ranges {\U10000-\UEFFFF} ;# all chars outside BMP range, excluding Private Use planes (F0000-10FFFF)
set re_barekey {^[}
foreach r $ranges {
append re_barekey $r
}
append re_barekey {]+$}
proc is_barekey {str} {
if {[tcl::string::length $str] == 0} {
return 0
}
variable re_barekey
return [regexp $re_barekey $str]
}
#test only that the characters in str are valid for the toml specified type 'integer'. #test only that the characters in str are valid for the toml specified type 'integer'.
proc int_validchars1 {str} { proc int_validchars1 {str} {
set numchars [tcl::string::length $str] set numchars [tcl::string::length $str]
@ -3471,7 +3580,7 @@ namespace eval tomlish::parse {
return 1 return 1
} }
barekey { barekey {
error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z_-\] allowed. [tomlish::parse::report_line]" error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z0-9_-\] and a selection of letter-like chars allowed (see tomlish::utils::is_barekey). [tomlish::parse::report_line]"
} }
whitespace { whitespace {
# hash marks end of whitespace token # hash marks end of whitespace token
@ -5222,7 +5331,7 @@ namespace eval tomlish::parse {
if {[tomlish::utils::is_barekey $c]} { if {[tomlish::utils::is_barekey $c]} {
append tok $c append tok $c
} else { } else {
error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z0-9_-\] allowed. [tomlish::parse::report_line]" error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z0-9_-\] and a selection of letter-like chars allowed. (see tomlish::utils::is_barekey) [tomlish::parse::report_line]"
} }
} }
starttablename - starttablearrayname { starttablename - starttablearrayname {
@ -5354,10 +5463,15 @@ namespace eval tomlish::dict {
namespace export {[a-z]*}; # Convention: export all lowercase namespace export {[a-z]*}; # Convention: export all lowercase
namespace path [namespace parent] namespace path [namespace parent]
proc is_tomltype {d} { proc is_tomlish_typeval {d} {
expr {[dict size $d] == 2 && [dict exists $d type] && [dict exists $d value]} #designed to detect {type <tag> value <whatever>} e.g {type INT value 3}, {type STRING value "blah etc"}
#as a sanity check we need to avoid mistaking user data that happens to match same form
#consider x.y={type="spud",value="blah"}
#The value of type will itself have already been converted to {type STRING value spud} ie never a single element.
#check the length of the type as a quick way to see it's a tag - not something else masqerading.
expr {[dict size $d] == 2 && [dict exists $d type] && [dict exists $d value] && [llength [dict get $d type]] == 1}
} }
proc is_tomltype2 {d} { proc is_tomlish_typeval2 {d} {
upvar ::tomlish::tags tags upvar ::tomlish::tags tags
expr {[lindex $d 0] eq "type" && [lindex $d 1] in $tags} expr {[lindex $d 0] eq "type" && [lindex $d 1] in $tags}
} }
@ -5366,7 +5480,7 @@ namespace eval tomlish::dict {
set dictposn [expr {[dict size $d] -1}] set dictposn [expr {[dict size $d] -1}]
foreach k [lreverse [dict keys $d]] { foreach k [lreverse [dict keys $d]] {
set dval [dict get $d $k] set dval [dict get $d $k]
if {[is_tomltype $dval]} { if {[is_tomlish_typeval $dval]} {
set last_simple $dictposn set last_simple $dictposn
break break
} }

6002
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/tomlish-1.1.3.tm

File diff suppressed because it is too large Load Diff

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

@ -2,12 +2,15 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1}
}
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
puts " Punk Boot" puts " Punk Boot"
puts $hashline\n puts $hashline\n
package prefer latest package prefer latest
lassign [split [info tclversion] .] tclmajorv tclminorv lassign [split [info tclversion] .] tclmajorv tclminorv
global A ;#UI Ansi code array global A ;#UI Ansi code array
@ -104,7 +107,7 @@ namespace eval ::punkboot::lib {
} }
} }
return [join $newparts .] return [join $newparts .]
} }
proc tm_version_required_canonical {versionspec} { proc tm_version_required_canonical {versionspec} {
#also trim leading zero from any dottedpart? #also trim leading zero from any dottedpart?
#Tcl *allows* leading zeros in any of the dotted parts - but they are not significant. #Tcl *allows* leading zeros in any of the dotted parts - but they are not significant.
@ -112,10 +115,10 @@ namespace eval ::punkboot::lib {
#also 1b3 == 1b0003 #also 1b3 == 1b0003
if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version
set errmsg "punkboot::lib::tm_version_required_canonical - invalid version specification" set errmsg "punkboot::lib::tm_version_required_canonical - invalid version specification"
if {[string first - $versionspec] < 0} { if {[string first - $versionspec] < 0} {
#no dash #no dash
#looks like a minbounded version (ie a single version with no dash) convert to min-max form #looks like a minbounded version (ie a single version with no dash) convert to min-max form
set from $versionspec set from $versionspec
if {![::punkboot::lib::tm_version_isvalid $from]} { if {![::punkboot::lib::tm_version_isvalid $from]} {
error "$errmsg '$versionpec'" error "$errmsg '$versionpec'"
@ -127,7 +130,7 @@ namespace eval ::punkboot::lib {
error "$errmsg '$versionspec'" error "$errmsg '$versionspec'"
} }
} else { } else {
# min- or min-max # min- or min-max
#validation and canonicalisation (strip leading zeroes from each segment, including either side of a or b) #validation and canonicalisation (strip leading zeroes from each segment, including either side of a or b)
set parts [split $versionspec -] ;#we expect only 2 parts set parts [split $versionspec -] ;#we expect only 2 parts
lassign $parts from to lassign $parts from to
@ -162,18 +165,18 @@ if {"::try" ni [info commands ::try]} {
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#Module loading from src/bootsupport or [pwd]/modules if pwd is a 'src' folder #Module loading from src/bootsupport or [pwd]/modules if pwd is a 'src' folder
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#If there is a folder under the current directory, in the subpath src/bootsupport/modules which contains .tm files #If there is a folder under the current directory, in the subpath src/bootsupport/modules which contains .tm files
# - then it will attempt to preference these modules # - then it will attempt to preference these modules
# This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script
# and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables
set startdir [pwd] set startdir [pwd]
#we are focussed on pure-tcl libs/modules in bootsupport for now. #we are focussed on pure-tcl libs/modules in bootsupport for now.
#There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc
#REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries
# - we need to support that without binary downloads from repos unless the user explicitly asks for that. # - we need to support that without binary downloads from repos unless the user explicitly asks for that.
# - They may already be available in the vfs (or pointed to package paths) of the running executable. # - They may already be available in the vfs (or pointed to package paths) of the running executable.
# - todo: some user prompting regarding installs with platform-appropriate package managers # - todo: some user prompting regarding installs with platform-appropriate package managers
# - todo: some user prompting regarding building accelerators from source. # - todo: some user prompting regarding building accelerators from source.
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
set bootsupport_module_paths [list] set bootsupport_module_paths [list]
@ -209,7 +212,7 @@ if {[file tail $startdir] eq "src"} {
#todo - other src 'module' dirs.. #todo - other src 'module' dirs..
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] { foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] {
if {[file exists $p]} { if {[file exists $p]} {
lappend sourcesupport_module_paths $p lappend sourcesupport_module_paths $p
} }
} }
# -- -- -- # -- -- --
@ -219,7 +222,7 @@ if {[file tail $startdir] eq "src"} {
} }
} }
# -- -- -- # -- -- --
foreach p [list {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] { foreach p [list {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] {
if {[file exists $p]} { if {[file exists $p]} {
set sourcesupport_paths_exist 1 set sourcesupport_paths_exist 1
@ -228,7 +231,7 @@ if {[file tail $startdir] eq "src"} {
} }
if {$sourcesupport_paths_exist} { if {$sourcesupport_paths_exist} {
#launch from <projectdir/src is also likely to be common #launch from <projectdir/src is also likely to be common
# but we need to be loud about what's going on. # but we need to be loud about what's going on.
puts stderr "------------------------------------------------------------------" puts stderr "------------------------------------------------------------------"
puts stderr "Launched from within a folder ending in 'src'" puts stderr "Launched from within a folder ending in 'src'"
@ -238,7 +241,7 @@ if {[file tail $startdir] eq "src"} {
} }
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
set package_paths_modified 0 set package_paths_modified 0
if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
set original_tm_list [tcl::tm::list] set original_tm_list [tcl::tm::list]
tcl::tm::remove {*}$original_tm_list tcl::tm::remove {*}$original_tm_list
@ -254,7 +257,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
} }
} }
set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk] ;#packages we set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk]
if {$support_contents_exist} { if {$support_contents_exist} {
#only forget all *unloaded* package names #only forget all *unloaded* package names
foreach pkg [package names] { foreach pkg [package names] {
@ -270,9 +273,9 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
package forget $pkg package forget $pkg
} }
} }
#tcl::tm::add {*}$original_tm_list {*}$bootsupport_module_paths {*}$sourcesupport_module_paths #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] #set ::auto_path [list {*}$original_auto_path {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths] set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
} }
puts "----> auto_path $::auto_path" puts "----> auto_path $::auto_path"
@ -281,18 +284,19 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
#package require Thread #package require Thread
# - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly.
# tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list
#These are strong dependencies #These are strong dependencies
package forget punk::mix package forget punk::mix
package forget punk::repo package forget punk::repo
package forget punkcheck package forget punkcheck
package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies
package require punk::mix package require punk::mix
package require punkcheck package require punkcheck
package require punk::lib package require punk::lib
package require punk::args
package require punk::ansi
set package_paths_modified 1 set package_paths_modified 1
@ -302,11 +306,12 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
set ::punkboot::pkg_requirements_found [list] set ::punkboot::pkg_requirements_found [list]
#we will treat 'package require <mver>.<etc>' (minbounded) as <mver>.<etc>-<mver+1> ie explicitly convert to corresponding bounded form #we will treat 'package require <mver>.<etc>' (minbounded) as <mver>.<etc>-<mver+1> ie explicitly convert to corresponding bounded form
#put some with leading zeros to test normalisation #put some with leading zeros to test normalisation
set ::punkboot::bootsupport_requirements [dict create\ set ::punkboot::bootsupport_requirements [dict create\
punk::repo [list version "00.01.01-"]\ punk::repo [list version "00.01.01-"]\
punk::mix [list version ""]\ punk::mix [list version ""]\
punk::ansi [list]\ punk::ansi [list]\
punk::args [list]\
overtype [list version "1.6.5-"]\ overtype [list version "1.6.5-"]\
punkcheck [list]\ punkcheck [list]\
fauxlink [list version "0.1.1-"]\ fauxlink [list version "0.1.1-"]\
@ -322,7 +327,7 @@ dict for {pkg pkginfo} $::punkboot::bootsupport_requirements {
if {![catch {::punkboot::lib::tm_version_required_canonical $ver} canonical]} { if {![catch {::punkboot::lib::tm_version_required_canonical $ver} canonical]} {
if {$canonical ne $ver} { if {$canonical ne $ver} {
dict set pkginfo version $canonical ;# plain ver mapped to min-max. min- and min-max and empty left as is dict set pkginfo version $canonical ;# plain ver mapped to min-max. min- and min-max and empty left as is
dict set ::punkboot::bootsupport_requirements $pkg $pkginfo dict set ::punkboot::bootsupport_requirements $pkg $pkginfo
} }
} else { } else {
puts stderr "punkboot::bootsupport_requirements - package $pkg has invalid version specification '$ver'" puts stderr "punkboot::bootsupport_requirements - package $pkg has invalid version specification '$ver'"
@ -331,9 +336,9 @@ dict for {pkg pkginfo} $::punkboot::bootsupport_requirements {
} else { } else {
#make sure each has a blank version entry if nothing was there. #make sure each has a blank version entry if nothing was there.
dict set pkginfo version "" dict set pkginfo version ""
dict set ::punkboot::bootsupport_requirements $pkg $pkginfo dict set ::punkboot::bootsupport_requirements $pkg $pkginfo
} }
} }
#Assert - our bootsupport_requirement version numbers should now be either empty or of the form min- or min-max #Assert - our bootsupport_requirement version numbers should now be either empty or of the form min- or min-max
#dict for {k v} $::punkboot::bootsupport_requirements { #dict for {k v} $::punkboot::bootsupport_requirements {
# puts "- $k $v" # puts "- $k $v"
@ -356,7 +361,7 @@ set ::punkboot::bootsupport_recommended [dict create\
# create an interp in which we hijack package command # create an interp in which we hijack package command
# This allows us to auto-gather some dependencies (not necessarily all and not necessarily strictly required) # This allows us to auto-gather some dependencies (not necessarily all and not necessarily strictly required)
# Note: even in a separate interp we could still possibly get side-effects if a package has compiled components - REVIEW # Note: even in a separate interp we could still possibly get side-effects if a package has compiled components - REVIEW
# Hopefully the only side-effect is that a subsequent load of the package will be faster... # Hopefully the only side-effect is that a subsequent load of the package will be faster...
# (punk boot is intended to operate without compiled components - but some could be pulled in by tcl modules if they're found) # (punk boot is intended to operate without compiled components - but some could be pulled in by tcl modules if they're found)
# (tcllibc is also highly desirable as the performance impact when not available can be dramatic.) # (tcllibc is also highly desirable as the performance impact when not available can be dramatic.)
# ... but if the binary is loaded with a different path name when we come to actually use it - there could be issues. # ... but if the binary is loaded with a different path name when we come to actually use it - there could be issues.
@ -378,7 +383,7 @@ proc ::punkboot::check_package_availability {args} {
#best effort at auto-determinining packages required (dependencies) based on top-level packages in the list. #best effort at auto-determinining packages required (dependencies) based on top-level packages in the list.
#Without fully parsing the package-loading Tcl scripts and examining all side-effects (an unlikely capability), #Without fully parsing the package-loading Tcl scripts and examining all side-effects (an unlikely capability),
# this is not going to be as accurate as the package developer providing a definitive list of which packages are required and which are optional. # this is not going to be as accurate as the package developer providing a definitive list of which packages are required and which are optional.
# 'optionality' is a contextual concept anyway depending on how the package is intended to be used. # 'optionality' is a contextual concept anyway depending on how the package is intended to be used.
# The package developer may consider a feature optional - but it may not be optional in a particular usecase. # The package developer may consider a feature optional - but it may not be optional in a particular usecase.
set bootsupport_requirements [lindex $args end] set bootsupport_requirements [lindex $args end]
@ -484,7 +489,7 @@ proc ::punkboot::check_package_availability {args} {
#should still distinguish: {pkgname {}} -valid vs {pkgname {{}}} due to empty string supplied in call - invalid - but leave for underlying package command to error on #should still distinguish: {pkgname {}} -valid vs {pkgname {{}}} due to empty string supplied in call - invalid - but leave for underlying package command to error on
set pkgrequest [list $pkgname $requirements_list] set pkgrequest [list $pkgname $requirements_list]
if {$pkgrequest ni $::test::pkg_requested} { if {$pkgrequest ni $::test::pkg_requested} {
lappend ::test::pkg_requested $pkgrequest lappend ::test::pkg_requested $pkgrequest
} }
# -- -- --- --- --- --- --- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- -- --- --- --- --- --- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
@ -507,13 +512,13 @@ proc ::punkboot::check_package_availability {args} {
} }
if {[llength $::test::pkg_stack]} { if {[llength $::test::pkg_stack]} {
set caller [lindex $::test::pkg_stack end] set caller [lindex $::test::pkg_stack end]
set required_by [dict get $pinfo required_by] set required_by [dict get $pinfo required_by]
if {$caller ni $required_by} { if {$caller ni $required_by} {
lappend required_by $caller lappend required_by $caller
} }
dict set pinfo required_by $required_by dict set pinfo required_by $required_by
} }
lappend ::test::pkg_stack $pkgname lappend ::test::pkg_stack $pkgname
#At this point we could short circuit if we've already classified this package/requirements combo as missing/broken from a previous require #At this point we could short circuit if we've already classified this package/requirements combo as missing/broken from a previous require
#review - there is some chance the exact pkg/requirements combo may succeed after an earlier failure if some package adjusted search paths.. #review - there is some chance the exact pkg/requirements combo may succeed after an earlier failure if some package adjusted search paths..
@ -527,23 +532,23 @@ proc ::punkboot::check_package_availability {args} {
#use our normalised requirements instead of original args #use our normalised requirements instead of original args
#if {[catch [list ::package_orig {*}$args] result]} {} #if {[catch [list ::package_orig {*}$args] result]} {}
if {[catch [list ::package_orig require $pkgname {*}$requirements_list] result]} { if {[catch [list ::package_orig require $pkgname {*}$requirements_list] result]} {
dict set pinfo testerror $result dict set pinfo testerror $result
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " \x1b\[93m$result\x1b\[m"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
#to determine the version that we attempted to load, #to determine the version that we attempted to load,
#- we need to look at 'pkg versions' vs -exact / ver / ver-ver (using package vsatisfies) #- we need to look at 'pkg versions' vs -exact / ver / ver-ver (using package vsatisfies)
if {![llength $versions]} { if {![llength $versions]} {
#no versions *and* we had an error - missing is our best guess. review. #no versions *and* we had an error - missing is our best guess. review.
#'package versions Tcl' never shows any results #'package versions Tcl' never shows any results
#so requests for old versions will show as missing not broken. #so requests for old versions will show as missing not broken.
#This is probably better anyway. #This is probably better anyway.
if {$pkgrequest ni $::test::pkg_missing} { if {$pkgrequest ni $::test::pkg_missing} {
@ -572,21 +577,21 @@ proc ::punkboot::check_package_availability {args} {
lappend selectable_versions $v lappend selectable_versions $v
} }
} else { } else {
#we are operating under 'package prefer' = latest #we are operating under 'package prefer' = latest
set selectable_versions $ordered_versions set selectable_versions $ordered_versions
} }
if {[llength $requirements_list]} { if {[llength $requirements_list]} {
#add one or no entry for each requirement. #add one or no entry for each requirement.
#pick highest at end #pick highest at end
set satisfiers [list] set satisfiers [list]
foreach requirement $requirements_list { foreach requirement $requirements_list {
foreach ver [lreverse $selectable_versions] { foreach ver [lreverse $selectable_versions] {
if {[package vsatisfies $ver $requirement]} { if {[package vsatisfies $ver $requirement]} {
lappend satisfiers $ver lappend satisfiers $ver
break break
} }
} }
} }
if {[llength $satisfiers]} { if {[llength $satisfiers]} {
set satisfiers [lsort -command {::package_orig vcompare} $satisfiers] set satisfiers [lsort -command {::package_orig vcompare} $satisfiers]
@ -622,7 +627,7 @@ proc ::punkboot::check_package_availability {args} {
if {![catch {::package_orig files Tcl} ]} { if {![catch {::package_orig files Tcl} ]} {
#tcl9 (also some 8.6/8.7) has 'package files' subcommand. #tcl9 (also some 8.6/8.7) has 'package files' subcommand.
#unfortunately, in some cases (e.g md5 when no accelerators available) this can be a huge list (1000+) showing all scanned pkgIndex.tcl files from unrelated packages. #unfortunately, in some cases (e.g md5 when no accelerators available) this can be a huge list (1000+) showing all scanned pkgIndex.tcl files from unrelated packages.
#We expect this to be fixed - but early Tcl9 (and some 8.6/8.7) versions may persist and have this behaviour #We expect this to be fixed - but early Tcl9 (and some 8.6/8.7) versions may persist and have this behaviour
#see: https://core.tcl-lang.org/tcl/tktview/209fd9adce #see: https://core.tcl-lang.org/tcl/tktview/209fd9adce
set all_files [::package_orig files $pkgname] set all_files [::package_orig files $pkgname]
#some arbitrary threshold? REVIEW #some arbitrary threshold? REVIEW
@ -637,7 +642,7 @@ proc ::punkboot::check_package_availability {args} {
dict set pinfo packagefiles {} ;#default dict set pinfo packagefiles {} ;#default
#there are all sorts of scripts, so this is not predictably structured #there are all sorts of scripts, so this is not predictably structured
#e.g using things like apply #e.g using things like apply
#we will attempt to get a trailing source .. <file> #we will attempt to get a trailing source .. <file>
set parts [split [string trim $ifneeded_script] {;}] set parts [split [string trim $ifneeded_script] {;}]
set trimparts [list] set trimparts [list]
foreach p $parts { foreach p $parts {
@ -648,7 +653,7 @@ proc ::punkboot::check_package_availability {args} {
if {$last_with_text ne "" && [regexp -- {\S+$} $last_with_text lastword]} { if {$last_with_text ne "" && [regexp -- {\S+$} $last_with_text lastword]} {
#if it's a file or dir - close enough (?) #if it's a file or dir - close enough (?)
#e.g tcllibc uses apply and the last entry is actuall a folder used to find the file.. #e.g tcllibc uses apply and the last entry is actuall a folder used to find the file..
#we aren't brave enough to try to work out the actual file(s) #we aren't brave enough to try to work out the actual file(s)
if {[file exists $lastword]} { if {[file exists $lastword]} {
dict set pinfo packagefiles $lastword dict set pinfo packagefiles $lastword
} }
@ -662,10 +667,10 @@ proc ::punkboot::check_package_availability {args} {
return [uplevel 1 [list ::package_orig {*}$args]] return [uplevel 1 [list ::package_orig {*}$args]]
} }
} }
set ::test::pkg_stack [list] set ::test::pkg_stack [list]
catch {::package_orig require zzz-non-existant} ;#scan so we get 'package versions' results catch {::package_orig require zzz-non-existant} ;#scan so we get 'package versions' results
dict for {pkg pkgdict} $::test::bootsupport_requirements { dict for {pkg pkgdict} $::test::bootsupport_requirements {
#set nsquals [namespace qualifiers $pkg] #set nsquals [namespace qualifiers $pkg]
#if {$nsquals ne ""} { #if {$nsquals ne ""} {
# catch {::package_orig require ${nsquals}::zzz-non-existant} ;#force scan of every level encountered # catch {::package_orig require ${nsquals}::zzz-non-existant} ;#force scan of every level encountered
@ -690,7 +695,7 @@ proc ::punkboot::check_package_availability {args} {
# set ver [package provide $pkg] # set ver [package provide $pkg]
# if {$ver eq ""} { # if {$ver eq ""} {
# #puts stderr "missing pkg: $pkg" # #puts stderr "missing pkg: $pkg"
# lappend ::test::pkg_missing $pkg # lappend ::test::pkg_missing $pkg
# } else { # } else {
# if {[string tolower $pkg] eq "tcl"} { # if {[string tolower $pkg] eq "tcl"} {
# #ignore # #ignore
@ -1180,17 +1185,17 @@ if {$::punkboot::command eq "check"} {
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
foreach fld [tcl::tm::list] { foreach fld [tcl::tm::list] {
if {[file exists $fld]} { if {[file exists $fld]} {
puts stdout " $fld" puts stdout " $fld"
} else { } else {
puts stdout " $fld (not present)" puts stdout " $fld (not present)"
} }
} }
puts stdout "- auto_path" puts stdout "- auto_path"
foreach fld $::auto_path { foreach fld $::auto_path {
if {[file exists $fld]} { if {[file exists $fld]} {
puts stdout " $fld" puts stdout " $fld"
} else { } else {
puts stdout " $fld (not present)" puts stdout " $fld (not present)"
} }
} }
flush stdout flush stdout
@ -1283,22 +1288,22 @@ if {$::punkboot::command eq "info"} {
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
puts stdout "- vendorlib folders: ([llength $vendorlibfolders])" puts stdout "- vendorlib folders: ([llength $vendorlibfolders])"
foreach fld $vendorlibfolders { foreach fld $vendorlibfolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
puts stdout "- vendormodule folders: ([llength $vendormodulefolders])" puts stdout "- vendormodule folders: ([llength $vendormodulefolders])"
foreach fld $vendormodulefolders { foreach fld $vendormodulefolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot] set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot]
puts stdout "- source module paths: [llength $source_module_folderlist]" puts stdout "- source module paths: [llength $source_module_folderlist]"
foreach fld $source_module_folderlist { foreach fld $source_module_folderlist {
puts stdout " $fld" puts stdout " $fld"
} }
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*] set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib lappend projectlibfolders lib
puts stdout "- source libary paths: [llength $projectlibfolders]" puts stdout "- source libary paths: [llength $projectlibfolders]"
foreach fld $projectlibfolders { foreach fld $projectlibfolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} { if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} {
set vc "fossil" set vc "fossil"
@ -1389,10 +1394,9 @@ if {$::punkboot::command eq "vendorupdate"} {
#todo vendor/lib #todo vendor/lib
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*] set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*]
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
lappend vendormodulefolders vendormodules #lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders { foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} {
lassign [split $vf _] _vm tclx lassign [split $vf _] _vm tclx
if {$tclx ne ""} { if {$tclx ne ""} {
set which _$tclx set which _$tclx
@ -1481,7 +1485,6 @@ if {$::punkboot::command eq "vendorupdate"} {
} else { } else {
puts stderr "No config at $vendor_config - nothing configured to update" puts stderr "No config at $vendor_config - nothing configured to update"
} }
}
} }
} }
@ -1508,105 +1511,102 @@ if {$::punkboot::command eq "bootsupport"} {
set bootsupport_modules [list] ;#variable populated by include_modules.config file - review set bootsupport_modules [list] ;#variable populated by include_modules.config file - review
set sourcefolder $projectroot/src set sourcefolder $projectroot/src
set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules_tcl*] set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules modules_tcl*]
lappend bootmodulefolders modules
foreach bm $bootmodulefolders { foreach bm $bootmodulefolders {
if {[file exists $sourcefolder/bootsupport/$bm]} { lassign [split $bm _] _bm tclx
lassign [split $bm _] _bm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
set which ""
}
set bootsupport_config $projectroot/src/bootsupport/modules$which/include_modules.config ;#
if {[file exists $bootsupport_config]} {
set targetroot $projectroot/src/bootsupport/modules$which
source $bootsupport_config ;#populate $bootsupport_modules with project-specific list
if {![llength $bootsupport_modules]} {
puts stderr "bootsupport/modules$which - No local bootsupport modules configured for updating"
} else { } else {
set which ""
}
set bootsupport_config $projectroot/src/bootsupport/modules$which/include_modules.config ;#
if {[file exists $bootsupport_config]} {
set targetroot $projectroot/src/bootsupport/modules$which
source $bootsupport_config ;#populate $bootsupport_modules with project-specific list
if {![llength $bootsupport_modules]} {
puts stderr "bootsupport/modules$which - No local bootsupport modules configured for updating"
} else {
if {[catch { if {[catch {
#---------- #----------
set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck] set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck]
$boot_installer set_source_target $projectroot $projectroot/src/bootsupport $boot_installer set_source_target $projectroot $projectroot/src/bootsupport
set boot_event [$boot_installer start_event {-make_step bootsupport}] set boot_event [$boot_installer start_event {-make_step bootsupport}]
#---------- #----------
} errM]} { } errM]} {
puts stderr "Unable to use punkcheck for bootsupport error: $errM" puts stderr "Unable to use punkcheck for bootsupport error: $errM"
set boot_event "" set boot_event ""
} }
foreach {relpath modulematch} $bootsupport_modules { foreach {relpath modulematch} $bootsupport_modules {
set modulematch [string trim $modulematch :] set modulematch [string trim $modulematch :]
set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]] set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]]
set srclocation [file join $projectroot $relpath $module_subpath] set srclocation [file join $projectroot $relpath $module_subpath]
#puts stdout "$relpath $modulematch $module_subpath $srclocation" #puts stdout "$relpath $modulematch $module_subpath $srclocation"
if {[string first - $modulematch]} { if {[string first - $modulematch]} {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm] set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm]
} else { } else {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm] set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm]
} }
if {![llength $pkgmatches]} { if {![llength $pkgmatches]} {
puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation" puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation"
continue continue
} }
set modulematch_is_glob [regexp {[*?\[\]]} $modulematch] set modulematch_is_glob [regexp {[*?\[\]]} $modulematch]
if {!$modulematch_is_glob} { if {!$modulematch_is_glob} {
#if modulematch was specified without globs - only copy latest #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 #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 pkgmatches [lsort -command modfile_sort $pkgmatches]
set latestfile [lindex $pkgmatches end] set latestfile [lindex $pkgmatches end]
#set latestver [lindex [split [file rootname $latestfile] -] 1] #set latestver [lindex [split [file rootname $latestfile] -] 1]
set copy_files $latestfile set copy_files $latestfile
} else { } else {
#globs in modulematch - may be different packages matched by glob - copy all versions of matches #globs in modulematch - may be different packages matched by glob - copy all versions of matches
#review #review
set copy_files $pkgmatches set copy_files $pkgmatches
} }
foreach cfile $copy_files { foreach cfile $copy_files {
set srcfile [file join $srclocation $cfile] set srcfile [file join $srclocation $cfile]
set tgtfile [file join $targetroot $module_subpath $cfile] set tgtfile [file join $targetroot $module_subpath $cfile]
if {$boot_event ne ""} { if {$boot_event ne ""} {
#---------- #----------
$boot_event targetset_init INSTALL $tgtfile $boot_event targetset_init INSTALL $tgtfile
$boot_event targetset_addsource $srcfile $boot_event targetset_addsource $srcfile
#---------- #----------
if {\ if {\
[llength [dict get [$boot_event targetset_source_changes] changed]]\ [llength [dict get [$boot_event targetset_source_changes] changed]]\
|| [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\ || [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\
} { } {
file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists
$boot_event targetset_started $boot_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile"
if {[catch { if {[catch {
file copy -force $srcfile $tgtfile file copy -force $srcfile $tgtfile
} errM]} { } errM]} {
$boot_event targetset_end FAILED $boot_event targetset_end FAILED
} else {
$boot_event targetset_end OK
}
# -- --- --- --- --- ---
} else { } else {
puts -nonewline stderr "." $boot_event targetset_end OK
$boot_event targetset_end SKIPPED
} }
$boot_event end # -- --- --- --- --- ---
} else { } else {
file copy -force $srcfile $tgtfile puts -nonewline stderr "."
$boot_event targetset_end SKIPPED
} }
$boot_event end
} else {
file copy -force $srcfile $tgtfile
} }
} }
if {$boot_event ne ""} {
puts \n
$boot_event destroy
$boot_installer destroy
}
} }
if {$boot_event ne ""} {
puts \n
$boot_event destroy
$boot_installer destroy
}
} }
} }
} }
} }
@ -1699,59 +1699,53 @@ if {$::punkboot::command ni {project modules vfs}} {
#install src vendor contents (from version controlled src folder) to base of project (same target folders as our own src/modules etc ie to paths that go on the auto_path and in tcl::tm::list) #install src vendor contents (from version controlled src folder) to base of project (same target folders as our own src/modules etc ie to paths that go on the auto_path and in tcl::tm::list)
if {$::punkboot::command in {project modules}} { if {$::punkboot::command in {project modules}} {
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
lappend vendorlibfolders vendorlib
foreach lf $vendorlibfolders {
if {[file exists $sourcefolder/$lf]} {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
set which ""
}
set target_lib_folder $projectroot/lib$which
file mkdir $projectroot/lib$which
#exclude README.md from source folder - but only the root one
#-antiglob_paths takes relative patterns e.g
# */test.txt will only match test.txt exactly one level deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# **/test.txt will match at any level below the root (but not in the root)
set antipaths [list\
README.md\
]
puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
}
if {![llength $vendorlibfolders]} {
puts stderr "VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found."
}
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders { foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} { lassign [split $vf _] _vm tclx
lassign [split $vf _] _vm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
} else { set which ""
set which ""
}
set target_module_folder $projectroot/modules$which
file mkdir $target_module_folder
#install .tm *and other files*
puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} }
set target_module_folder $projectroot/modules$which
file mkdir $target_module_folder
#install .tm *and other files*
puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} }
if {![llength $vendormodulefolders]} { if {![llength $vendormodulefolders]} {
puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found." puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found."
} }
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib vendorlib_tcl*]
foreach lf $vendorlibfolders {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
set which ""
}
set target_lib_folder $projectroot/lib$which
file mkdir $projectroot/lib$which
#exclude README.md from source folder - but only the root one
#-antiglob_paths takes relative patterns e.g
# */test.txt will only match test.txt exactly one level deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# **/test.txt will match at any level below the root (but not in the root)
set antipaths [list\
README.md\
]
puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
if {![llength $vendorlibfolders]} {
puts stderr "VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found."
}
######################################################## ########################################################
#templates #templates
#e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync #e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync
@ -1760,10 +1754,10 @@ if {$::punkboot::command in {project modules}} {
set old_layout_update_list [list\ set old_layout_update_list [list\
[list project $sourcefolder/modules/punk/mix/templates]\ [list project $sourcefolder/modules/punk/mix/templates]\
[list basic $sourcefolder/mixtemplates]\ [list basic $sourcefolder/mixtemplates]\
] ]
set layout_bases [list\ set layout_bases [list\
$sourcefolder/project_layouts/custom/_project\ $sourcefolder/project_layouts/custom/_project\
] ]
foreach layoutbase $layout_bases { foreach layoutbase $layout_bases {
if {![file exists $layoutbase]} { if {![file exists $layoutbase]} {
@ -1823,27 +1817,25 @@ if {$::punkboot::command in {project modules}} {
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*] set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib lappend projectlibfolders lib
foreach lf $projectlibfolders { foreach lf $projectlibfolders {
if {[file exists $sourcefolder/$lf]} { lassign [split $lf _] _vm tclx
lassign [split $lf _] _vm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
} else { set which ""
set which "" }
} set target_lib_folder $projectroot/lib$which
set target_lib_folder $projectroot/lib$which file mkdir $projectroot/lib$which
file mkdir $projectroot/lib$which #exclude README.md from source folder - but only the root one
#exclude README.md from source folder - but only the root one #-antiglob_paths takes relative patterns e.g
#-antiglob_paths takes relative patterns e.g # */test.txt will only match test.txt exactly one level deep.
# */test.txt will only match test.txt exactly one level deep. # */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep. # **/test.txt will match at any level below the root (but not in the root)
# **/test.txt will match at any level below the root (but not in the root) set antipaths [list\
set antipaths [list\ README.md\
README.md\ ]
] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] puts stdout [punkcheck::summarize_install_resultdict $resultdict]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
} }
if {![llength $projectlibfolders]} { if {![llength $projectlibfolders]} {
puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found." puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found."
@ -2355,7 +2347,7 @@ foreach vfstail $vfs_tails {
} else { } else {
lappend runtimes $matchrt lappend runtimes $matchrt
} }
} }
} }
#assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config #assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config

1028
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/commandstack-0.3.tm

File diff suppressed because it is too large Load Diff

21
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm

@ -20,7 +20,7 @@
#[manpage_begin fauxlink_module_fauxlink 0 0.1.1] #[manpage_begin fauxlink_module_fauxlink 0 0.1.1]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}] #[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}]
#[moddesc {fauxlink .fxlnk}] [comment {-- Description at end of page heading --}] #[moddesc {.fauxlink .fxlnk}] [comment {-- Description at end of page heading --}]
#[require fauxlink] #[require fauxlink]
#[keywords symlink faux fake shortcut toml] #[keywords symlink faux fake shortcut toml]
#[description] #[description]
@ -29,18 +29,19 @@
#[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as #[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as
#[para] archiving and packaging systems. #[para] archiving and packaging systems.
#[para] The target is specified in a minimally-encoded form in the filename itself - but still human readable. #[para] The target is specified in a minimally-encoded form in the filename itself - but still human readable.
#[para] format of name <nominalname>#<encodedtarget>.fxlnk #[para] format of name <nominalname>#<encodedtarget>.fauxlink
#[para] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget> #[para] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget>
#[para] The file extension must be .fauxlink or .fxlnk
#[para] The + symbol substitutes for forward-slashes. #[para] The + symbol substitutes for forward-slashes.
#[para] Other chars can be encoded using url-like encoding - (but only up to %7E !) #[para] Other chars can be encoded using url-like encoding - (but only up to %7E !)
#[para] We deliberately treat higher % sequences literally. #[para] We deliberately treat higher % sequences literally.
#[para] This means actual uri::urn encoded unicode sequences (e.g %E2%99%A5 [lb]heart[rb]) can remain literal for linking to urls. #[para] This means actual uri::urn encoded unicode sequences (e.g %E2%99%A5 [lb]heart[rb]) can remain literal for linking to urls.
#[para] e.g if an actual + or # is required in a filename or path segment they can be encoded as %2B & %23 #[para] e.g if an actual + or # is required in a filename or path segment they can be encoded as %2B & %23
#[para] e.g a link to a file file#A.txt in parent dir could be: #[para] e.g a link to a file file#A.txt in parent dir could be:
#[para] file%23A.txt#..+file%23A.txt.fxlnk #[para] file%23A.txt#..+file%23A.txt.fauxlink
#[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fxlnk #[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fauxlink
#[para] The <nominalname> can be unrelated to the actual target #[para] The <nominalname> can be unrelated to the actual target
#[para] e.g datafile.dat#..+file%23A.txt.fxlnk #[para] e.g datafile.dat#..+file%23A.txt.fauxlink
#[para] This system has no filesystem support - and must be completely application driven. #[para] This system has no filesystem support - and must be completely application driven.
#[para] This can be useful for example in application test packages which may be tarred or zipped and moved cross platform. #[para] This can be useful for example in application test packages which may be tarred or zipped and moved cross platform.
#[para] The target being fully specified in the name means the file doesn't have to be read for the target to be determined #[para] The target being fully specified in the name means the file doesn't have to be read for the target to be determined
@ -63,9 +64,9 @@
#https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/url-encoding-unc-paths-not-url-decoded #https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/url-encoding-unc-paths-not-url-decoded
# ie "//server/c/Program files" works but "//server/c/Program%20Files" is now treated by windows as a literal path with %20 in it. # ie "//server/c/Program files" works but "//server/c/Program%20Files" is now treated by windows as a literal path with %20 in it.
#Using fauxlink - a link would be: #Using fauxlink - a link would be:
# "my-program-files#++server+c+Program%20Files.fxlnk" # "my-program-files#++server+c+Program%20Files.fauxlink"
#If we needed the old-style literal %20 it would become #If we needed the old-style literal %20 it would become
# "my-program-files#++server+c+Program%2520Files.fxlnk" # "my-program-files#++server+c+Program%2520Files.fauxlink"
# #
# The file:// scheme on windows supposedly *does* decode %xx (for use in a browser) # The file:// scheme on windows supposedly *does* decode %xx (for use in a browser)
# e.g # e.g
@ -296,12 +297,12 @@ namespace eval fauxlink {
set is_fauxlink 0 set is_fauxlink 0
#we'll process anyway - but return the result wrapped #we'll process anyway - but return the result wrapped
#This should allow deliberate erroring for the calling dict user if the extension difference is inadvertent #This should allow deliberate erroring for the calling dict user if the extension difference is inadvertent
#(e.g blindly processing all files in a folder that is normally only .fxlnk files - but then something added that happens #(e.g blindly processing all files in a folder that is normally only .fauxlink files - but then something added that happens
# to have # characters in it) # to have # characters in it)
#It also means if someone really wants to use the fauxlink semantics on a different file type #It also means if someone really wants to use the fauxlink semantics on a different file type
# - they can - but just have to access the results differently and take that (minor) risk. # - they can - but just have to access the results differently and take that (minor) risk.
#error "fauxlink::resolve refusing to process link $link - file extension must be .fxlnk or .fauxlink" #error "fauxlink::resolve refusing to process link $link - file extension must be .fxlnk or .fauxlink"
set err_extra "\nnonstandard extension '$extension_name' for fauxlink. Check that the call to fauxlink::resolve was deliberate" set err_extra "\nnonstandard extension '$extension_name' for fauxlink. (expected .fxlnk or .fauxlink) Check that the call to fauxlink::resolve was deliberate"
} else { } else {
set is_fauxlink 1 set is_fauxlink 1
set err_extra "" set err_extra ""
@ -318,7 +319,7 @@ namespace eval fauxlink {
#if there are 4 parts - the 3rd part is a tagset where each tag begins with @ #if there are 4 parts - the 3rd part is a tagset where each tag begins with @
#and each subsequent part is a comment. Empty comments are stripped from the comments list #and each subsequent part is a comment. Empty comments are stripped from the comments list
#A tagset can be empty - but if it's not empty it must contain at least one @ and must start with @ #A tagset can be empty - but if it's not empty it must contain at least one @ and must start with @
#e.g name.txt#path#@tag1@tag2#test###.fxlnk #e.g name.txt#path#@tag1@tag2#test###.fauxlink
#has a name, a target, 2 tags and one comment #has a name, a target, 2 tags and one comment
#check namespec already has required chars encoded #check namespec already has required chars encoded

12822
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/metaface-1.2.5.tm

File diff suppressed because it is too large Load Diff

1288
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm

File diff suppressed because it is too large Load Diff

1508
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm

File diff suppressed because it is too large Load Diff

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

@ -2469,7 +2469,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
if {$pretty} { if {$pretty} {
#return [pdict -channel none sgr_cache */%str,%ansiview] #return [pdict -channel none sgr_cache */%str,%ansiview]
return [pdict -channel none sgr_cache */%rpadstr-"sample",%ansiviewstyle] return [punk::lib::pdict -channel none sgr_cache */%rpadstr-"sample",%ansiviewstyle]
} }
if {[catch { if {[catch {
@ -5116,6 +5116,7 @@ tcl::namespace::eval punk::ansi::ta {
# arrow keys -> ESC O A, ESC O B, ESC O C, ESC O D # 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) # plus more for auxiliary keypad codes in keypad application mode (and some in numeric mode)
#regexp expanded syntax = ?x
variable re_ansi_detect {(?x) 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)))) (?:\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) |(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)

65
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm

@ -108,8 +108,10 @@ namespace eval punk::cap::handlers::templates {
#todo - handle wrapped or unwrapped tarjar files - in which case we have to adjust tmfolder appropriately #todo - handle wrapped or unwrapped tarjar files - in which case we have to adjust tmfolder appropriately
#set tpath [file normalize [file join $tmfile [dict get $capdict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder #set tpath [file normalize [file join $tmfile [dict get $capdict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder
set projectinfo [punk::repo::find_repos $tmfolder] #set projectinfo [punk::repo::find_repos $tmfolder] ;#slow - REVIEW
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $tmfolder]
#store the projectbase even if it's empty string #store the projectbase even if it's empty string
set extended_capdict $capdict set extended_capdict $capdict
set resolved_path [file join $tmfolder $path] set resolved_path [file join $tmfolder $path]
@ -148,8 +150,9 @@ namespace eval punk::cap::handlers::templates {
return 0 return 0
} }
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase] #set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
@ -166,8 +169,9 @@ namespace eval punk::cap::handlers::templates {
return 0 return 0
} }
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase] #set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
@ -183,8 +187,9 @@ namespace eval punk::cap::handlers::templates {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' which doesn't seem to exist" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' which doesn't seem to exist"
return 0 return 0
} }
set projectinfo [punk::repo::find_repos $normpath] #set projectinfo [punk::repo::find_repos $normpath]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $normpath]
#todo - verify no other provider has registered same absolute path - if sharing a project-external location is needed - they need their own subfolder #todo - verify no other provider has registered same absolute path - if sharing a project-external location is needed - they need their own subfolder
set extended_capdict $capdict set extended_capdict $capdict
@ -244,6 +249,18 @@ namespace eval punk::cap::handlers::templates {
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
namespace export * namespace export *
namespace eval class { namespace eval class {
variable PUNKARGS
#set argd [punk::args::get_dict {
# @id -id "::punk::cap::handlers::templates::class::api folders"
# -startdir -default ""
# @values -max 0
#} $args]
lappend PUNKARGS [list {
@id -id "::punk::cap::handlers::templates::class::api folders"
-startdir -default ""
@values -max 0
}]
oo::class create api { oo::class create api {
#return a dict keyed on folder with source pkg as value #return a dict keyed on folder with source pkg as value
constructor {capname} { constructor {capname} {
@ -253,11 +270,8 @@ namespace eval punk::cap::handlers::templates {
set capabilityname $capname set capabilityname $capname
} }
method folders {args} { method folders {args} {
set argd [punk::args::get_dict { #puts "--folders $args"
@id -id "::punk::cap::handlers::templates::class::api folders" set argd [punk::args::parse $args withid "::punk::cap::handlers::templates::class::api folders"]
-startdir -default ""
@values -max 0
} $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set opt_startdir [dict get $opts -startdir] set opt_startdir [dict get $opts -startdir]
@ -270,6 +284,10 @@ namespace eval punk::cap::handlers::templates {
set startdir $opt_startdir set startdir $opt_startdir
} }
} }
set searchbase $startdir
#set pathinfo [punk::repo::find_repos $searchbase] ;#relatively slow! REVIEW - pass as arg? cache?
#set pwd_projectroot [dict get $pathinfo closest]
set pwd_projectroot [punk::repo::find_project $searchbase]
variable capabilityname variable capabilityname
@ -314,9 +332,9 @@ namespace eval punk::cap::handlers::templates {
set module_projectroot [dict get $capdecl_extended projectbase] set module_projectroot [dict get $capdecl_extended projectbase]
dict lappend found_paths_module $vendor [list pkg $pkg path [dict get $capdecl_extended resolved_path] pathtype $pathtype projectbase $module_projectroot] dict lappend found_paths_module $vendor [list pkg $pkg path [dict get $capdecl_extended resolved_path] pathtype $pathtype projectbase $module_projectroot]
} elseif {$pathtype eq "currentproject_multivendor"} { } elseif {$pathtype eq "currentproject_multivendor"} {
set searchbase $startdir #set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase] #set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest] #set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} { if {$pwd_projectroot ne ""} {
set deckbase [file join $pwd_projectroot $path] set deckbase [file join $pwd_projectroot $path]
if {![file exists $deckbase]} { if {![file exists $deckbase]} {
@ -349,9 +367,9 @@ namespace eval punk::cap::handlers::templates {
} }
} }
} elseif {$pathtype eq "currentproject"} { } elseif {$pathtype eq "currentproject"} {
set searchbase $startdir #set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase] #set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest] #set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} { if {$pwd_projectroot ne ""} {
#path relative to projectroot already validated by handler as being within a currentproject_multivendor tree #path relative to projectroot already validated by handler as being within a currentproject_multivendor tree
set targetfolder [file join $pwd_projectroot $path] set targetfolder [file join $pwd_projectroot $path]
@ -489,8 +507,9 @@ namespace eval punk::cap::handlers::templates {
set refdict [my get_itemdict_projectlayoutrefs {*}$args] set refdict [my get_itemdict_projectlayoutrefs {*}$args]
set layoutdict [dict create] set layoutdict [dict create]
set projectinfo [punk::repo::find_repos $searchbase] #set projectinfo [punk::repo::find_repos $searchbase]
set projectroot [dict get $projectinfo closest] #set projectroot [dict get $projectinfo closest]
set projectroot [punk::repo::find_project $searchbase]
dict for {layoutname refinfo} $refdict { dict for {layoutname refinfo} $refdict {
set templatepathtype [dict get $refinfo sourceinfo pathtype] set templatepathtype [dict get $refinfo sourceinfo pathtype]
@ -760,6 +779,10 @@ namespace eval punk::cap::handlers::templates {
} }
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::cap::handlers::templates ::punk::cap::handlers::templates::class
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready

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

@ -1,487 +1,487 @@
tcl::namespace::eval punk::config { tcl::namespace::eval punk::config {
variable loaded variable loaded
variable startup ;#include env overrides variable startup ;#include env overrides
variable running variable running
variable punk_env_vars variable punk_env_vars
variable other_env_vars variable other_env_vars
variable vars variable vars
namespace export {[a-z]*} namespace export {[a-z]*}
#todo - XDG_DATA_HOME etc #todo - XDG_DATA_HOME etc
#https://specifications.freedesktop.org/basedir-spec/latest/ #https://specifications.freedesktop.org/basedir-spec/latest/
# see also: http://hiphish.github.io/blog/2020/08/30/dotfiles-were-a-mistake/ # see also: http://hiphish.github.io/blog/2020/08/30/dotfiles-were-a-mistake/
proc init {} { proc init {} {
variable defaults variable defaults
variable startup variable startup
variable running variable running
variable punk_env_vars variable punk_env_vars
variable punk_env_vars_config variable punk_env_vars_config
variable other_env_vars variable other_env_vars
variable other_env_vars_config variable other_env_vars_config
set exename "" set exename ""
catch { catch {
#catch for safe interps #catch for safe interps
#safe base will return empty string, ordinary safe interp will raise error #safe base will return empty string, ordinary safe interp will raise error
set exename [tcl::info::nameofexecutable] set exename [tcl::info::nameofexecutable]
} }
if {$exename ne ""} { if {$exename ne ""} {
set exefolder [file dirname $exename] set exefolder [file dirname $exename]
#default file logs to logs folder at same level as exe if writable, or empty string #default file logs to logs folder at same level as exe if writable, or empty string
set log_folder [file normalize $exefolder/../logs] ;#~2ms set log_folder [file normalize $exefolder/../logs] ;#~2ms
#tcl::dict::set startup scriptlib $exefolder/scriptlib #tcl::dict::set startup scriptlib $exefolder/scriptlib
#tcl::dict::set startup apps $exefolder/../../punkapps #tcl::dict::set startup apps $exefolder/../../punkapps
#todo - use punk main.tcl location instead - exefolder doesn't work if system tclsh used etc #todo - use punk main.tcl location instead - exefolder doesn't work if system tclsh used etc
set default_scriptlib $exefolder/scriptlib set default_scriptlib $exefolder/scriptlib
set default_apps $exefolder/../../punkapps set default_apps $exefolder/../../punkapps
if {[file isdirectory $log_folder] && [file writable $log_folder]} { if {[file isdirectory $log_folder] && [file writable $log_folder]} {
#tcl::dict::set startup logfile_stdout $log_folder/repl-exec-stdout.txt #tcl::dict::set startup logfile_stdout $log_folder/repl-exec-stdout.txt
#tcl::dict::set startup logfile_stderr $log_folder/repl-exec-stderr.txt #tcl::dict::set startup logfile_stderr $log_folder/repl-exec-stderr.txt
set default_logfile_stdout $log_folder/repl-exec-stdout.txt set default_logfile_stdout $log_folder/repl-exec-stdout.txt
set default_logfile_stderr $log_folder/repl-exec-stderr.txt set default_logfile_stderr $log_folder/repl-exec-stderr.txt
} else { } else {
set default_logfile_stdout "" set default_logfile_stdout ""
set default_logfile_stderr "" set default_logfile_stderr ""
} }
} else { } else {
#probably a safe interp - which cannot access info nameofexecutable even if access given to the location via punk::island #probably a safe interp - which cannot access info nameofexecutable even if access given to the location via punk::island
#review - todo? #review - todo?
#tcl::dict::set startup scriptlib "" #tcl::dict::set startup scriptlib ""
#tcl::dict::set startup apps "" #tcl::dict::set startup apps ""
set default_scriptlib "" set default_scriptlib ""
set default_apps "" set default_apps ""
set default_logfile_stdout "" set default_logfile_stdout ""
set default_logfile_stderr "" set default_logfile_stderr ""
} }
# auto_exec_mechanism ;#whether to use exec instead of experimental shellfilter::run # auto_exec_mechanism ;#whether to use exec instead of experimental shellfilter::run
#optional channel transforms on stdout/stderr. #optional channel transforms on stdout/stderr.
#can sometimes be useful to distinguish eventloop stdout/stderr writes compared to those triggered directly from repl commands #can sometimes be useful to distinguish eventloop stdout/stderr writes compared to those triggered directly from repl commands
#If no distinction necessary - should use default_color_<chan> #If no distinction necessary - should use default_color_<chan>
#The counterpart: default_color_<chan>_repl is a transform that is added and removed with each repl evaluation. #The counterpart: default_color_<chan>_repl is a transform that is added and removed with each repl evaluation.
#startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default #startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default
set default_color_stdout brightwhite ;#stdout colour including background calls (after etc) set default_color_stdout brightwhite ;#stdout colour including background calls (after etc)
set default_color_stdout_repl "" ;#stdout colour applied during direct repl call only set default_color_stdout_repl "" ;#stdout colour applied during direct repl call only
#This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful. #This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful.
#set default_color_stderr "red bold" #set default_color_stderr "red bold"
#set default_color_stderr "web-lightsalmon" #set default_color_stderr "web-lightsalmon"
set default_color_stderr yellow ;#limit to basic colours for wider terminal support. yellow = term-olive set default_color_stderr yellow ;#limit to basic colours for wider terminal support. yellow = term-olive
set default_color_stderr_repl "" ;#during repl call only set default_color_stderr_repl "" ;#during repl call only
set homedir "" set homedir ""
if {[catch { if {[catch {
#depending on which build of tcl - some safe interps prior to bugfix https://core.tcl-lang.org/tcl/info/3aa487993f will return a homedir value in an unmodified safe interp #depending on which build of tcl - some safe interps prior to bugfix https://core.tcl-lang.org/tcl/info/3aa487993f will return a homedir value in an unmodified safe interp
#other 'safe' interps may have explicitly made this available - we shouldn't override that decision here using interp issafe so we can't compensate for versions which shouldn't really be returning this in the safe interp #other 'safe' interps may have explicitly made this available - we shouldn't override that decision here using interp issafe so we can't compensate for versions which shouldn't really be returning this in the safe interp
set homedir [file home] set homedir [file home]
} errM]} { } errM]} {
#tcl 8.6 doesn't have file home.. try again #tcl 8.6 doesn't have file home.. try again
if {[info exists ::env(HOME)]} { if {[info exists ::env(HOME)]} {
set homedir $::env(HOME) set homedir $::env(HOME)
} }
} }
# per user xdg vars # per user xdg vars
# --- # ---
set default_xdg_config_home "" ;#config data - portable set default_xdg_config_home "" ;#config data - portable
set default_xdg_data_home "" ;#data the user likely to want to be portable set default_xdg_data_home "" ;#data the user likely to want to be portable
set default_xdg_cache_home "" ;#local cache set default_xdg_cache_home "" ;#local cache
set default_xdg_state_home "" ;#persistent user data such as logs, but not as important or as portable as those in xdg_data_home set default_xdg_state_home "" ;#persistent user data such as logs, but not as important or as portable as those in xdg_data_home
# --- # ---
set default_xdg_data_dirs "" ;#non-user specific set default_xdg_data_dirs "" ;#non-user specific
#xdg_config_dirs ? #xdg_config_dirs ?
#xdg_runtime_dir ? #xdg_runtime_dir ?
#review. we are assuming if we can't get a home dir - then all the xdg vars including xdg_data_dirs aren't likely to be useful (as presumably filesystem access is absent) #review. we are assuming if we can't get a home dir - then all the xdg vars including xdg_data_dirs aren't likely to be useful (as presumably filesystem access is absent)
#(safe interp generally won't have access to ::env either) #(safe interp generally won't have access to ::env either)
#This coupling doesn't necessarily hold - its possible the relevant env vars were copied to a safe interp - although that would be a policy that would make disabling 'info home' inconsistent. #This coupling doesn't necessarily hold - its possible the relevant env vars were copied to a safe interp - although that would be a policy that would make disabling 'info home' inconsistent.
if {$homedir ne ""} { if {$homedir ne ""} {
if {"windows" eq $::tcl_platform(platform)} { if {"windows" eq $::tcl_platform(platform)} {
#as much as I'd prefer to use ~/.local/share and ~/.config to keep them more consistent with unixlike platforms - the vast majority of apps put them where microsoft wants them. #as much as I'd prefer to use ~/.local/share and ~/.config to keep them more consistent with unixlike platforms - the vast majority of apps put them where microsoft wants them.
#we have a choice of LOCALAPPDATA vs APPDATA (local to machine vs potentially roaming/redirected in a corporate environment) #we have a choice of LOCALAPPDATA vs APPDATA (local to machine vs potentially roaming/redirected in a corporate environment)
#using the roaming location should not impact users who aren't using a domain controller but is potentially much more convenient for those who do. #using the roaming location should not impact users who aren't using a domain controller but is potentially much more convenient for those who do.
if {[info exists ::env(APPDATA)]} { if {[info exists ::env(APPDATA)]} {
set default_xdg_config_home $::env(APPDATA) set default_xdg_config_home $::env(APPDATA)
set default_xdg_data_home $::env(APPDATA) set default_xdg_data_home $::env(APPDATA)
} }
#The xdg_cache_home should be kept local #The xdg_cache_home should be kept local
if {[info exists ::env(LOCALAPPDATA)]} { if {[info exists ::env(LOCALAPPDATA)]} {
set default_xdg_cache_home $::env(LOCALAPPDATA) set default_xdg_cache_home $::env(LOCALAPPDATA)
set default_xdg_state_home $::env(LOCALAPPDATA) set default_xdg_state_home $::env(LOCALAPPDATA)
} }
if {[info exists ::env(PROGRAMDATA)]} { if {[info exists ::env(PROGRAMDATA)]} {
#- equiv env(ALLUSERSPROFILE) ? #- equiv env(ALLUSERSPROFILE) ?
set default_xdg_data_dirs $::env(PROGRAMDATA) set default_xdg_data_dirs $::env(PROGRAMDATA)
} }
} else { } else {
#follow defaults as specified on freedesktop.org e.g https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html #follow defaults as specified on freedesktop.org e.g https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
set default_xdg_config_home [file join $homedir .config] set default_xdg_config_home [file join $homedir .config]
set default_xdg_data_home [file join $homedir .local share] set default_xdg_data_home [file join $homedir .local share]
set default_xdg_cache_home [file join $homedir .cache] set default_xdg_cache_home [file join $homedir .cache]
set default_xdg_state_home [file join $homedir .local state] set default_xdg_state_home [file join $homedir .local state]
set default_xdg_data_dirs /usr/local/share set default_xdg_data_dirs /usr/local/share
} }
} }
set defaults [dict create\ set defaults [dict create\
apps $default_apps\ apps $default_apps\
config ""\ config ""\
configset ".punkshell"\ configset ".punkshell"\
scriptlib $default_scriptlib\ scriptlib $default_scriptlib\
color_stdout $default_color_stdout\ color_stdout $default_color_stdout\
color_stdout_repl $default_color_stdout_repl\ color_stdout_repl $default_color_stdout_repl\
color_stderr $default_color_stderr\ color_stderr $default_color_stderr\
color_stderr_repl $default_color_stderr_repl\ color_stderr_repl $default_color_stderr_repl\
logfile_stdout $default_logfile_stdout\ logfile_stdout $default_logfile_stdout\
logfile_stderr $default_logfile_stderr\ logfile_stderr $default_logfile_stderr\
logfile_active 0\ logfile_active 0\
syslog_stdout "127.0.0.1:514"\ syslog_stdout "127.0.0.1:514"\
syslog_stderr "127.0.0.1:514"\ syslog_stderr "127.0.0.1:514"\
syslog_active 0\ syslog_active 0\
auto_exec_mechanism exec\ auto_exec_mechanism exec\
auto_noexec 0\ auto_noexec 0\
xdg_config_home $default_xdg_config_home\ xdg_config_home $default_xdg_config_home\
xdg_data_home $default_xdg_data_home\ xdg_data_home $default_xdg_data_home\
xdg_cache_home $default_xdg_cache_home\ xdg_cache_home $default_xdg_cache_home\
xdg_state_home $default_xdg_state_home\ xdg_state_home $default_xdg_state_home\
xdg_data_dirs $default_xdg_data_dirs\ xdg_data_dirs $default_xdg_data_dirs\
theme_posh_override ""\ theme_posh_override ""\
posh_theme ""\ posh_theme ""\
posh_themes_path ""\ posh_themes_path ""\
] ]
set startup $defaults set startup $defaults
#load values from saved config file - $xdg_config_home/punk/punk.config ? #load values from saved config file - $xdg_config_home/punk/punk.config ?
#typically we want env vars to override the stored config - as env vars conventionally used on some commandlines. #typically we want env vars to override the stored config - as env vars conventionally used on some commandlines.
#that's possibly ok for the PUNK_ vars #that's possibly ok for the PUNK_ vars
#however.. others like the xdg vars and NOCOLOR may apply to other apps.. and we may want to override them from the saved config? #however.. others like the xdg vars and NOCOLOR may apply to other apps.. and we may want to override them from the saved config?
#making some env vars override saved config values and some not would be potentially confusing. may need one/more specific settings or env vars to determine which takes precedence? #making some env vars override saved config values and some not would be potentially confusing. may need one/more specific settings or env vars to determine which takes precedence?
#simpler is probably just to let env vars take precedence - and warn when saving or viewing config that the saved values are being overridden #simpler is probably just to let env vars take precedence - and warn when saving or viewing config that the saved values are being overridden
#- requiring user to manually unset any unwanted env vars when launching? #- requiring user to manually unset any unwanted env vars when launching?
#we are likely to want the saved configs for subshells/decks to override them however. #we are likely to want the saved configs for subshells/decks to override them however.
#todo - load/save config file #todo - load/save config file
#todo - define which configvars are settable in env #todo - define which configvars are settable in env
#list of varname varinfo where varinfo is a sub dictionary (type key is mandatory, with value from: string,pathlist,boolean) #list of varname varinfo where varinfo is a sub dictionary (type key is mandatory, with value from: string,pathlist,boolean)
set punk_env_vars_config [dict create \ set punk_env_vars_config [dict create \
PUNK_APPS {type pathlist}\ PUNK_APPS {type pathlist}\
PUNK_CONFIG {type string}\ PUNK_CONFIG {type string}\
PUNK_CONFIGSET {type string}\ PUNK_CONFIGSET {type string}\
PUNK_SCRIPTLIB {type string}\ PUNK_SCRIPTLIB {type string}\
PUNK_AUTO_EXEC_MECHANISM {type string}\ PUNK_AUTO_EXEC_MECHANISM {type string}\
PUNK_AUTO_NOEXEC {type string default 0 help "set 1 to set Tcl's ::auto_noexec true.\nStops 'unknown' from running external programs"}\ PUNK_AUTO_NOEXEC {type string default 0 help "set 1 to set Tcl's ::auto_noexec true.\nStops 'unknown' from running external programs"}\
PUNK_COLOR_STDERR {type string help "stderr colour transform. Use 'punk::ansi::a?' to see colour names"}\ PUNK_COLOR_STDERR {type string help "stderr colour transform. Use 'punk::ansi::a?' to see colour names"}\
PUNK_COLOR_STDERR_REPL {type string help "stderr colour transform only while command running (not active during 'after')"}\ PUNK_COLOR_STDERR_REPL {type string help "stderr colour transform only while command running (not active during 'after')"}\
PUNK_COLOR_STDOUT {type string help "stdout colour transform. Use 'punk::ansi::a?' to see colour names"}\ PUNK_COLOR_STDOUT {type string help "stdout colour transform. Use 'punk::ansi::a?' to see colour names"}\
PUNK_COLOR_STDOUT_REPL {type string help "stdout colour transform only while command running (not active during 'after')"}\ PUNK_COLOR_STDOUT_REPL {type string help "stdout colour transform only while command running (not active during 'after')"}\
PUNK_LOGFILE_STDOUT {type string}\ PUNK_LOGFILE_STDOUT {type string}\
PUNK_LOGFILE_STDERR {type string}\ PUNK_LOGFILE_STDERR {type string}\
PUNK_LOGFILE_ACTIVE {type string}\ PUNK_LOGFILE_ACTIVE {type string}\
PUNK_SYSLOG_STDOUT {type string}\ PUNK_SYSLOG_STDOUT {type string}\
PUNK_SYSLOG_STDERR {type string}\ PUNK_SYSLOG_STDERR {type string}\
PUNK_SYSLOG_ACTIVE {type string}\ PUNK_SYSLOG_ACTIVE {type string}\
PUNK_THEME_POSH_OVERRIDE {type string}\ PUNK_THEME_POSH_OVERRIDE {type string}\
] ]
set punk_env_vars [dict keys $punk_env_vars_config] set punk_env_vars [dict keys $punk_env_vars_config]
#override with env vars if set #override with env vars if set
foreach {evar varinfo} $punk_env_vars_config { foreach {evar varinfo} $punk_env_vars_config {
if {[info exists ::env($evar)]} { if {[info exists ::env($evar)]} {
set vartype [dict get $varinfo type] set vartype [dict get $varinfo type]
set f [set ::env($evar)] set f [set ::env($evar)]
if {$f ne "default"} { if {$f ne "default"} {
#e.g PUNK_SCRIPTLIB -> scriptlib #e.g PUNK_SCRIPTLIB -> scriptlib
set varname [tcl::string::tolower [tcl::string::range $evar 5 end]] set varname [tcl::string::tolower [tcl::string::range $evar 5 end]]
if {$vartype eq "pathlist"} { if {$vartype eq "pathlist"} {
#colon vs semicolon path sep is problematic for windows environments where unix-like systems such as cygwin/wsl are used and a variable may be set for either the native path separator or the unix-like system #colon vs semicolon path sep is problematic for windows environments where unix-like systems such as cygwin/wsl are used and a variable may be set for either the native path separator or the unix-like system
#Even without the colon vs semicolon issue, native vs unix-like paths on windows mixed environment systems can cause grief. #Even without the colon vs semicolon issue, native vs unix-like paths on windows mixed environment systems can cause grief.
#For now at least, we will simply respect the platform pathSeparator and hope the user manages the environment variables appropriately. #For now at least, we will simply respect the platform pathSeparator and hope the user manages the environment variables appropriately.
#some programs do automatic translation - which is a nice idea in principle - but is also prone to error as we don't know if it's already occurred or not depending on how things are launched. #some programs do automatic translation - which is a nice idea in principle - but is also prone to error as we don't know if it's already occurred or not depending on how things are launched.
#An example of where this sort of thing can go wrong is env(TCLLIBPATH) - which is defined as a space separated list not requiring further splitting #An example of where this sort of thing can go wrong is env(TCLLIBPATH) - which is defined as a space separated list not requiring further splitting
# - but some programs have been known to split this value on colon anyway, which breaks things on windows. # - but some programs have been known to split this value on colon anyway, which breaks things on windows.
set paths [split $f $::tcl_platform(pathSeparator)] set paths [split $f $::tcl_platform(pathSeparator)]
set final [list] set final [list]
#eliminate empty values (leading or trailing or extraneous separators) #eliminate empty values (leading or trailing or extraneous separators)
foreach p $paths { foreach p $paths {
if {[tcl::string::trim $p] ne ""} { if {[tcl::string::trim $p] ne ""} {
lappend final $p lappend final $p
} }
} }
tcl::dict::set startup $varname $final tcl::dict::set startup $varname $final
} else { } else {
tcl::dict::set startup $varname $f tcl::dict::set startup $varname $f
} }
} }
} }
} }
# https://no-color.org # https://no-color.org
#if {[info exists ::env(NO_COLOR)]} { #if {[info exists ::env(NO_COLOR)]} {
# if {$::env(NO_COLOR) ne ""} { # if {$::env(NO_COLOR) ne ""} {
# set colour_disabled 1 # set colour_disabled 1
# } # }
#} #}
set other_env_vars_config [dict create\ set other_env_vars_config [dict create\
NO_COLOR {type string}\ NO_COLOR {type string}\
XDG_CONFIG_HOME {type string}\ XDG_CONFIG_HOME {type string}\
XDG_DATA_HOME {type string}\ XDG_DATA_HOME {type string}\
XDG_CACHE_HOME {type string}\ XDG_CACHE_HOME {type string}\
XDG_STATE_HOME {type string}\ XDG_STATE_HOME {type string}\
XDG_DATA_DIRS {type pathlist}\ XDG_DATA_DIRS {type pathlist}\
POSH_THEME {type string}\ POSH_THEME {type string}\
POSH_THEMES_PATH {type string}\ POSH_THEMES_PATH {type string}\
TCLLIBPATH {type string}\ TCLLIBPATH {type string}\
] ]
lassign [split [info tclversion] .] tclmajorv tclminorv lassign [split [info tclversion] .] tclmajorv tclminorv
#don't rely on lseq or punk::lib for now.. #don't rely on lseq or punk::lib for now..
set relevant_minors [list] set relevant_minors [list]
for {set i 0} {$i <= $tclminorv} {incr i} { for {set i 0} {$i <= $tclminorv} {incr i} {
lappend relevant_minors $i lappend relevant_minors $i
} }
foreach minor $relevant_minors { foreach minor $relevant_minors {
set vname TCL${tclmajorv}_${minor}_TM_PATH set vname TCL${tclmajorv}_${minor}_TM_PATH
if {$minor eq $tclminorv || [info exists ::env($vname)]} { if {$minor eq $tclminorv || [info exists ::env($vname)]} {
dict set other_env_vars_config $vname {type string} dict set other_env_vars_config $vname {type string}
} }
} }
set other_env_vars [dict keys $other_env_vars_config] set other_env_vars [dict keys $other_env_vars_config]
foreach {evar varinfo} $other_env_vars_config { foreach {evar varinfo} $other_env_vars_config {
if {[info exists ::env($evar)]} { if {[info exists ::env($evar)]} {
set vartype [dict get $varinfo type] set vartype [dict get $varinfo type]
set f [set ::env($evar)] set f [set ::env($evar)]
if {$f ne "default"} { if {$f ne "default"} {
set varname [tcl::string::tolower $evar] set varname [tcl::string::tolower $evar]
if {$vartype eq "pathlist"} { if {$vartype eq "pathlist"} {
set paths [split $f $::tcl_platform(pathSeparator)] set paths [split $f $::tcl_platform(pathSeparator)]
set final [list] set final [list]
#eliminate empty values (leading or trailing or extraneous separators) #eliminate empty values (leading or trailing or extraneous separators)
foreach p $paths { foreach p $paths {
if {[tcl::string::trim $p] ne ""} { if {[tcl::string::trim $p] ne ""} {
lappend final $p lappend final $p
} }
} }
tcl::dict::set startup $varname $final tcl::dict::set startup $varname $final
} else { } else {
tcl::dict::set startup $varname $f tcl::dict::set startup $varname $f
} }
} }
} }
} }
#unset -nocomplain vars #unset -nocomplain vars
#todo #todo
set running [tcl::dict::create] set running [tcl::dict::create]
set running [tcl::dict::merge $running $startup] set running [tcl::dict::merge $running $startup]
} }
init init
#todo #todo
proc Apply {config} { proc Apply {config} {
puts stderr "punk::config::Apply partially implemented" puts stderr "punk::config::Apply partially implemented"
set configname [string map {-config ""} $config] set configname [string map {-config ""} $config]
if {$configname in {startup running}} { if {$configname in {startup running}} {
upvar ::punk::config::$configname applyconfig upvar ::punk::config::$configname applyconfig
if {[dict exists $applyconfig auto_noexec]} { if {[dict exists $applyconfig auto_noexec]} {
set auto [dict get $applyconfig auto_noexec] set auto [dict get $applyconfig auto_noexec]
if {![string is boolean -strict $auto]} { if {![string is boolean -strict $auto]} {
error "config::Apply error - invalid data for auto_noexec:'$auto' - expected boolean" error "config::Apply error - invalid data for auto_noexec:'$auto' - expected boolean"
} }
if {$auto} { if {$auto} {
set ::auto_noexec 1 set ::auto_noexec 1
} else { } else {
#puts "auto_noexec false" #puts "auto_noexec false"
unset -nocomplain ::auto_noexec unset -nocomplain ::auto_noexec
} }
} }
} else { } else {
error "no config named '$config' found" error "no config named '$config' found"
} }
return "apply done" return "apply done"
} }
Apply startup Apply startup
#todo - consider how to divide up settings, categories, 'devices', decks etc #todo - consider how to divide up settings, categories, 'devices', decks etc
proc get_running_global {varname} { proc get_running_global {varname} {
variable running variable running
if {[dict exists $running $varname]} { if {[dict exists $running $varname]} {
return [dict get $running $varname] return [dict get $running $varname]
} }
error "No such global configuration item '$varname' found in running config" error "No such global configuration item '$varname' found in running config"
} }
proc get_startup_global {varname} { proc get_startup_global {varname} {
variable startup variable startup
if {[dict exists $startup $varname]} { if {[dict exists $startup $varname]} {
return [dict get $startup $varname] return [dict get $startup $varname]
} }
error "No such global configuration item '$varname' found in startup config" error "No such global configuration item '$varname' found in startup config"
} }
proc get {whichconfig {globfor *}} { proc get {whichconfig {globfor *}} {
variable startup variable startup
variable running variable running
switch -- $whichconfig { switch -- $whichconfig {
config - startup - startup-config - startup-configuration { config - startup - startup-config - startup-configuration {
#show *startup* config - different behaviour may be confusing to those used to router startup and running configs #show *startup* config - different behaviour may be confusing to those used to router startup and running configs
set configdata $startup set configdata $startup
} }
running - running-config - running-configuration { running - running-config - running-configuration {
set configdata $running set configdata $running
} }
default { default {
error "Unknown config name '$whichconfig' - try startup or running" error "Unknown config name '$whichconfig' - try startup or running"
} }
} }
if {$globfor eq "*"} { if {$globfor eq "*"} {
return $configdata return $configdata
} else { } else {
set keys [dict keys $configdata [string tolower $globfor]] set keys [dict keys $configdata [string tolower $globfor]]
set filtered [dict create] set filtered [dict create]
foreach k $keys { foreach k $keys {
dict set filtered $k [dict get $configdata $k] dict set filtered $k [dict get $configdata $k]
} }
return $filtered return $filtered
} }
} }
proc configure {args} { proc configure {args} {
set argdef { set argdef {
@id -id ::punk::config::configure @id -id ::punk::config::configure
@cmd -name punk::config::configure -help\ @cmd -name punk::config::configure -help\
"UNIMPLEMENTED" "UNIMPLEMENTED"
@values -min 1 -max 1 @values -min 1 -max 1
whichconfig -type string -choices {startup running stop} whichconfig -type string -choices {startup running stop}
} }
set argd [punk::args::get_dict $argdef $args] set argd [punk::args::get_dict $argdef $args]
return "unimplemented - $argd" return "unimplemented - $argd"
} }
proc show {whichconfig {globfor *}} { proc show {whichconfig {globfor *}} {
#todo - tables for console #todo - tables for console
set configdata [punk::config::get $whichconfig $globfor] set configdata [punk::config::get $whichconfig $globfor]
return [punk::lib::showdict $configdata] return [punk::lib::showdict $configdata]
} }
#e.g #e.g
# copy running-config startup-config # copy running-config startup-config
# copy startup-config test-config.cfg # copy startup-config test-config.cfg
# copy backup-config.cfg running-config # copy backup-config.cfg running-config
#review - consider the merge vs overwrite feature of some routers.. where copy to running-config does a merge rather than an overwrite #review - consider the merge vs overwrite feature of some routers.. where copy to running-config does a merge rather than an overwrite
#This is to allow partial configs to be loaded to running, whereas a save of running to any target is always a complete configuration #This is to allow partial configs to be loaded to running, whereas a save of running to any target is always a complete configuration
proc copy {args} { proc copy {args} {
set argdef { set argdef {
@id -id ::punk::config::copy @id -id ::punk::config::copy
@cmd -name punk::config::copy -help\ @cmd -name punk::config::copy -help\
"Copy a partial or full configuration from one config to another "Copy a partial or full configuration from one config to another
If a target config has additional settings, then the source config can be considered to be partial with regards to the target. If a target config has additional settings, then the source config can be considered to be partial with regards to the target.
" "
-type -default "" -choices {replace merge} -help\ -type -default "" -choices {replace merge} -help\
"Defaults to merge when target is running-config "Defaults to merge when target is running-config
Defaults to replace when source is running-config" Defaults to replace when source is running-config"
@values -min 2 -max 2 @values -min 2 -max 2
fromconfig -help\ fromconfig -help\
"running or startup or file name (not fully implemented)" "running or startup or file name (not fully implemented)"
toconfig -help\ toconfig -help\
"running or startup or file name (not fully implemented)" "running or startup or file name (not fully implemented)"
} }
set argd [punk::args::get_dict $argdef $args] set argd [punk::args::get_dict $argdef $args]
set fromconfig [dict get $argd values fromconfig] set fromconfig [dict get $argd values fromconfig]
set toconfig [dict get $argd values toconfig] set toconfig [dict get $argd values toconfig]
set fromconfig [string map {-config ""} $fromconfig] set fromconfig [string map {-config ""} $fromconfig]
set toconfig [string map {-config ""} $toconfig] set toconfig [string map {-config ""} $toconfig]
set copytype [dict get $argd opts -type] set copytype [dict get $argd opts -type]
#todo - warn & prompt if doing merge copy to startup #todo - warn & prompt if doing merge copy to startup
switch -exact -- $fromconfig-$toconfig { switch -exact -- $fromconfig-$toconfig {
running-startup { running-startup {
if {$copytype eq ""} { if {$copytype eq ""} {
set copytype replace ;#full configuration set copytype replace ;#full configuration
} }
if {$copytype eq "replace"} { if {$copytype eq "replace"} {
error "punk::config::copy error. full configuration copy from running to startup config not yet supported" error "punk::config::copy error. full configuration copy from running to startup config not yet supported"
} else { } else {
error "punk::config::copy error. merge configuration copy from running to startup config not yet supported" error "punk::config::copy error. merge configuration copy from running to startup config not yet supported"
} }
} }
startup-running { startup-running {
#default type merge - even though it's not always what is desired #default type merge - even though it's not always what is desired
if {$copytype eq ""} { if {$copytype eq ""} {
set copytype merge ;#load in a partial configuration set copytype merge ;#load in a partial configuration
} }
#warn/prompt either way #warn/prompt either way
if {$copytype eq "replace"} { if {$copytype eq "replace"} {
#some routers require use of a separate command for this branch. #some routers require use of a separate command for this branch.
#presumably to ensure the user doesn't accidentally load partials onto a running system #presumably to ensure the user doesn't accidentally load partials onto a running system
# #
error "punk::config::copy error. full configuration copy from startup to overwrite running config not supported" error "punk::config::copy error. full configuration copy from startup to overwrite running config not supported"
} else { } else {
error "punk::config::copy error. merge copy from possibly partial configuration: startup to running config not currently supported" error "punk::config::copy error. merge copy from possibly partial configuration: startup to running config not currently supported"
} }
} }
default { default {
error "punk::config::copy error. copy must from running to startup or startup to running. File sources/targets not yet supported" error "punk::config::copy error. copy must from running to startup or startup to running. File sources/targets not yet supported"
} }
} }
} }
} }
#todo - move to cli? #todo - move to cli?
::tcl::namespace::eval punk::config { ::tcl::namespace::eval punk::config {
#todo - something better - 'previous' rather than reverting to startup #todo - something better - 'previous' rather than reverting to startup
proc channelcolors {{onoff {}}} { proc channelcolors {{onoff {}}} {
variable running variable running
variable startup variable startup
if {![string length $onoff]} { if {![string length $onoff]} {
return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]] return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]]
} else { } else {
if {![string is boolean $onoff]} { if {![string is boolean $onoff]} {
error "channelcolors: invalid value $onoff - expected boolean: true|false|on|off|1|0|yes|no" error "channelcolors: invalid value $onoff - expected boolean: true|false|on|off|1|0|yes|no"
} }
if {$onoff} { if {$onoff} {
dict set running color_stdout [dict get $startup color_stdout] dict set running color_stdout [dict get $startup color_stdout]
dict set running color_stderr [dict get $startup color_stderr] dict set running color_stderr [dict get $startup color_stderr]
} else { } else {
dict set running color_stdout "" dict set running color_stdout ""
dict set running color_stderr "" dict set running color_stderr ""
} }
} }
return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]] return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]]
} }
} }
package provide punk::config [tcl::namespace::eval punk::config { package provide punk::config [tcl::namespace::eval punk::config {
variable version variable version
set version 0.1 set version 0.1
}] }]

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

@ -767,6 +767,8 @@ namespace eval punk::mix::base {
dict for {path pathinfo} $dict_path_cksum { dict for {path pathinfo} $dict_path_cksum {
puts "fill_relativecksums_from_base_and_relativepathdict-->$path REVIEW"
#review to see if we process same path repeatedly, so could avoid repeated 'file exists $fullpath' below by caching a glob
if {![dict exists $pathinfo cksum]} { if {![dict exists $pathinfo cksum]} {
dict set pathinfo cksum "" dict set pathinfo cksum ""
} else { } else {
@ -851,7 +853,7 @@ namespace eval punk::mix::base {
} }
} else { } else {
if {[file type $specifiedpath] eq "relative"} { if {[file pathtype $specifiedpath] eq "relative"} {
#if specifiedpath is relative - and we don't have a base, we now need to convert relative to cwd to an absolute path for storage #if specifiedpath is relative - and we don't have a base, we now need to convert relative to cwd to an absolute path for storage
set targetpath [file normalize $specifiedpath] set targetpath [file normalize $specifiedpath]
set storedpath $targetpath set storedpath $targetpath
@ -911,6 +913,7 @@ namespace eval punk::mix::base {
} }
#buildruntime.exe obsolete.. #buildruntime.exe obsolete..
puts stderr "warning obsolete? get_all_vfs_build_cksums 'buildruntime.exe'???"
set fullpath_buildruntime $buildfolder/buildruntime.exe set fullpath_buildruntime $buildfolder/buildruntime.exe
set ckinfo_buildruntime [cksum_path $fullpath_buildruntime] set ckinfo_buildruntime [cksum_path $fullpath_buildruntime]

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

@ -412,9 +412,9 @@ namespace eval punk::mix::cli {
set repopaths [punk::repo::find_repos [pwd]] set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos] set repos [dict get $repopaths repos]
if {![llength $repos]} { if {![llength $repos]} {
append result [dict get $repopaths warnings] append result [punk::ansi::a+ bold yellow][dict get $repopaths warnings][punk::ansi::a]
} else { } else {
append result [dict get $repopaths warnings] append result [punk::ansi::a+ bold yellow][dict get $repopaths warnings][punk::ansi::a]
lassign [lindex $repos 0] repopath repotypes lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} { if {"fossil" in $repotypes} {
#review - multiple process launches to fossil a bit slow on windows.. #review - multiple process launches to fossil a bit slow on windows..
@ -739,7 +739,7 @@ namespace eval punk::mix::cli {
} }
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "P"
set did_skip 1 set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record] #set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$build_event targetset_end SKIPPED $build_event targetset_end SKIPPED
@ -771,7 +771,7 @@ namespace eval punk::mix::cli {
$event targetset_end OK -note "zip modpod" $event targetset_end OK -note "zip modpod"
} }
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "p"
set did_skip 1 set did_skip 1
if {$is_interesting} { if {$is_interesting} {
puts stderr "$modulefile [$event targetset_source_changes]" puts stderr "$modulefile [$event targetset_source_changes]"
@ -893,7 +893,7 @@ namespace eval punk::mix::cli {
if {$is_interesting} { if {$is_interesting} {
puts stdout "skipping module $current_source_dir/$m - no change in sources detected" puts stdout "skipping module $current_source_dir/$m - no change in sources detected"
} }
puts -nonewline stderr "." puts -nonewline stderr "m"
set did_skip 1 set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record] #set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$event targetset_end SKIPPED $event targetset_end SKIPPED
@ -935,7 +935,7 @@ namespace eval punk::mix::cli {
#set file_record [punkcheck::installfile_finished_install $basedir $file_record] #set file_record [punkcheck::installfile_finished_install $basedir $file_record]
$event targetset_end OK -note "already versioned module" $event targetset_end OK -note "already versioned module"
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "f"
set did_skip 1 set did_skip 1
if {$is_interesting} { if {$is_interesting} {
puts stderr "$current_source_dir/$m [$event targetset_source_changes]" puts stderr "$current_source_dir/$m [$event targetset_source_changes]"
@ -951,7 +951,8 @@ namespace eval punk::mix::cli {
if {$CALLDEPTH >= $max_depth} { if {$CALLDEPTH >= $max_depth} {
set subdirs [list] set subdirs [list]
} else { } else {
set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *] set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *]
set targets_existing [glob -nocomplain -dir $target_module_dir -type d -tail {*}$subdirs]
} }
#puts stderr "subdirs: $subdirs" #puts stderr "subdirs: $subdirs"
foreach d $subdirs { foreach d $subdirs {
@ -965,7 +966,10 @@ namespace eval punk::mix::cli {
if {$skipdir} { if {$skipdir} {
continue continue
} }
if {![file exists $target_module_dir/$d]} { #if {![file exists $target_module_dir/$d]} {
# file mkdir $target_module_dir/$d
#}
if {$d ni $targets_existing} {
file mkdir $target_module_dir/$d file mkdir $target_module_dir/$d
} }
lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\ lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\

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

@ -49,7 +49,7 @@ namespace eval punk::mix::commandset::buildsuite {
set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok. set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok.
set s [lindex $path_parts end-1] set s [lindex $path_parts end-1]
set p [lindex $path_parts end] set p [lindex $path_parts end]
#This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl #This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl
#so we can't just use tail as dict key. We could assume last record is always total - but #so we can't just use tail as dict key. We could assume last record is always total - but
if {![string match -nocase $s $suite]} { if {![string match -nocase $s $suite]} {

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

@ -26,7 +26,7 @@ namespace eval punk::mix::commandset::debug {
namespace export get paths namespace export get paths
namespace path ::punk::mix::cli namespace path ::punk::mix::cli
#Except for 'get' - all debug commands should emit to stdout #Except for 'get' - all debug commands should emit to stdout
proc paths {} { proc paths {} {
set out "" set out ""
puts stdout "find_repos output:" puts stdout "find_repos output:"
@ -40,7 +40,7 @@ namespace eval punk::mix::commandset::debug {
set template_base_dict [punk::mix::base::lib::get_template_basefolders] set template_base_dict [punk::mix::base::lib::get_template_basefolders]
puts stdout "get_template_basefolders output:" puts stdout "get_template_basefolders output:"
pdict template_base_dict */* pdict template_base_dict */*
return return
} }
#call other debug command - but capture stdout as return value #call other debug command - but capture stdout as return value
@ -84,9 +84,9 @@ namespace eval punk::mix::commandset::debug {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug { package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

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

@ -26,8 +26,10 @@ namespace eval punk::mix::commandset::module {
namespace export * namespace export *
proc paths {} { proc paths {} {
set roots [punk::repo::find_repos ""] #set roots [punk::repo::find_repos ""]
set project [lindex [dict get $roots project] 0] #set project [lindex [dict get $roots project] 0]
set project [punk::repo::find_project ""]
if {$project ne ""} { if {$project ne ""} {
set is_project 1 set is_project 1
set searchbase $project set searchbase $project

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

@ -20,7 +20,7 @@
#[manpage_begin punkshell_module_punk::mix::commandset::project 0 0.1.0] #[manpage_begin punkshell_module_punk::mix::commandset::project 0 0.1.0]
#[copyright "2023"] #[copyright "2023"]
#[titledesc {dec commandset - project}] [comment {-- Name section and table of contents description --}] #[titledesc {dec commandset - project}] [comment {-- Name section and table of contents description --}]
#[moddesc {deck CLI commandset - project}] [comment {-- Description at end of page heading --}] #[moddesc {deck CLI commandset - project}] [comment {-- Description at end of page heading --}]
#[require punk::mix::commandset::project] #[require punk::mix::commandset::project]
#[description] #[description]
@ -29,25 +29,25 @@
#*** !doctools #*** !doctools
#[section Overview] #[section Overview]
#[para] overview of punk::mix::commandset::project #[para] overview of punk::mix::commandset::project
#[para]Import into an ensemble namespace similarly to the way it is done with punk::mix::cli e.g #[para]Import into an ensemble namespace similarly to the way it is done with punk::mix::cli e.g
#[example { #[example {
# namespace eval myproject::cli { # namespace eval myproject::cli {
# namespace export * # namespace export *
# namespace ensemble create # namespace ensemble create
# package require punk::overlay # package require punk::overlay
# #
# package require punk::mix::commandset::project # package require punk::mix::commandset::project
# punk::overlay::import_commandset project . ::punk::mix::commandset::project # punk::overlay::import_commandset project . ::punk::mix::commandset::project
# punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection # punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
# } # }
#}] #}]
#[para] Where the . in the above example is the prefix/command separator #[para] Where the . in the above example is the prefix/command separator
#[para]The prefix ('project' in the above example) can be any string desired to disambiguate commands imported from other commandsets. #[para]The prefix ('project' in the above example) can be any string desired to disambiguate commands imported from other commandsets.
#[para]The above results in the availability of the ensemble command: ::myproject::cli project.new, which is implemented in ::punk::mix::commandset::project::new #[para]The above results in the availability of the ensemble command: ::myproject::cli project.new, which is implemented in ::punk::mix::commandset::project::new
#[para]Similarly, procs under ::punk::mix::commandset::project::collection will be available as subcommands of the ensemble as <ensemblecommand> projects.<procname> #[para]Similarly, procs under ::punk::mix::commandset::project::collection will be available as subcommands of the ensemble as <ensemblecommand> projects.<procname>
#[para] #[para]
#[subsection Concepts] #[subsection Concepts]
#[para] see punk::overlay #[para] see punk::overlay
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -56,7 +56,7 @@
#*** !doctools #*** !doctools
#[subsection dependencies] #[subsection dependencies]
#[para] packages used by punk::mix::commandset::project #[para] packages used by punk::mix::commandset::project
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
@ -88,7 +88,7 @@ namespace eval punk::mix::commandset::project {
namespace export * namespace export *
#*** !doctools #*** !doctools
#[subsection {Namespace punk::mix::commandset::project}] #[subsection {Namespace punk::mix::commandset::project}]
#[para] core commandset functions for punk::mix::commandset::project #[para] core commandset functions for punk::mix::commandset::project
#[list_begin definitions] #[list_begin definitions]
proc _default {} { proc _default {} {
@ -133,7 +133,7 @@ namespace eval punk::mix::commandset::project {
proc new {newprojectpath_or_name args} { proc new {newprojectpath_or_name args} {
#*** !doctools #*** !doctools
# [call [fun new] [arg newprojectpath_or_name] [opt args]] # [call [fun new] [arg newprojectpath_or_name] [opt args]]
#new project structure - may be dedicated to one module, or contain many. #new project structure - may be dedicated to one module, or contain many.
#create minimal folder structure only by specifying in args: -modules {} #create minimal folder structure only by specifying in args: -modules {}
if {[file pathtype $newprojectpath_or_name] eq "absolute"} { if {[file pathtype $newprojectpath_or_name] eq "absolute"} {
set projectfullpath [file normalize $newprojectpath_or_name] set projectfullpath [file normalize $newprojectpath_or_name]
@ -185,7 +185,7 @@ namespace eval punk::mix::commandset::project {
if {$opt_force || $opt_update} { if {$opt_force || $opt_update} {
#generally undesirable to add default project module during an update. #generally undesirable to add default project module during an update.
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
@ -207,12 +207,12 @@ namespace eval punk::mix::commandset::project {
} }
#we don't assume 'unknown' is configured to run shell commands #we don't assume 'unknown' is configured to run shell commands
if {[string length [package provide shellrun]]} { if {[string length [package provide shellrun]]} {
set exitinfo [run {*}$scoop_prog install fossil] set exitinfo [run {*}$scoop_prog install fossil]
#scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use. #scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use.
puts stdout "scoop install fossil ran with result: $exitinfo" puts stdout "scoop install fossil ran with result: $exitinfo"
} else { } else {
puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )" puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )"
set result [exec {*}$scoop_prog install fossil] set result [exec {*}$scoop_prog install fossil]
puts stdout $result puts stdout $result
} }
catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"') catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"')
@ -304,7 +304,7 @@ namespace eval punk::mix::commandset::project {
} }
} }
set project_dir_exists [file exists $projectdir] set project_dir_exists [file exists $projectdir]
if {$project_dir_exists && !($opt_force || $opt_update)} { if {$project_dir_exists && !($opt_force || $opt_update)} {
puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template" puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template"
@ -332,7 +332,7 @@ namespace eval punk::mix::commandset::project {
puts stderr $warnmsg puts stderr $warnmsg
} }
set fossil_repo_file "" set fossil_repo_file ""
set is_fossil_root 0 set is_fossil_root 0
if {$project_dir_exists && [punk::repo::is_fossil_root $projectdir]} { if {$project_dir_exists && [punk::repo::is_fossil_root $projectdir]} {
set is_fossil_root 1 set is_fossil_root 1
@ -356,7 +356,7 @@ namespace eval punk::mix::commandset::project {
return return
} }
#review #review
set fossil_repo_file $repodb_folder/$projectname.fossil set fossil_repo_file $repodb_folder/$projectname.fossil
} }
if {$fossil_repo_file eq ""} { if {$fossil_repo_file eq ""} {
@ -378,7 +378,7 @@ namespace eval punk::mix::commandset::project {
file mkdir $projectdir file mkdir $projectdir
puts stdout ">>> about to call punkcheck::install $layout_path $projectdir" puts stdout ">>> about to call punkcheck::install $layout_path $projectdir"
set resultdict [dict create] set resultdict [dict create]
set antipaths [list\ set antipaths [list\
src/doc/*\ src/doc/*\
@ -394,10 +394,10 @@ 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 #default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized
if {$opt_force} { if {$opt_force} {
puts stdout "copying layout files - with force applied - overwrite all-targets" puts stdout "copying layout files - with force applied - overwrite all-targets"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -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 { } else {
puts stdout "copying layout files - (if source file changed)" puts stdout "copying layout files - (if source file changed)"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -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] puts stdout [punkcheck::summarize_install_resultdict $resultdict]
@ -410,10 +410,10 @@ namespace eval punk::mix::commandset::project {
puts stdout "no src/doc in source template - update not required" 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. #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. #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*"] ## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"]
set override_antiglob_dir_core [list #* _aside .git] set override_antiglob_dir_core [list #* _aside .git]
if {[file exists $layout_path/.fossil-custom]} { if {[file exists $layout_path/.fossil-custom]} {
puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)" 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 -createempty 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]
@ -430,9 +430,9 @@ namespace eval punk::mix::commandset::project {
puts stdout "no .fossil-settings in source template - update not required" puts stdout "no .fossil-settings in source template - update not required"
} }
#scan all files in template #scan all files in template
# #
#TODO - deck command to substitute templates? #TODO - deck command to substitute templates?
set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout]
set stripprefix [file normalize $layout_path] set stripprefix [file normalize $layout_path]
@ -440,7 +440,7 @@ namespace eval punk::mix::commandset::project {
if {[llength $templatefiles]} { if {[llength $templatefiles]} {
puts stdout "Filling template file placeholders with the following tag map:" puts stdout "Filling template file placeholders with the following tag map:"
foreach {placeholder value} $tagmap { foreach {placeholder value} $tagmap {
puts stdout " $placeholder -> $value" puts stdout " $placeholder -> $value"
} }
} }
foreach templatefullpath $templatefiles { foreach templatefullpath $templatefiles {
@ -452,7 +452,7 @@ namespace eval punk::mix::commandset::project {
set data2 [string map $tagmap $data] set data2 [string map $tagmap $data]
if {$data2 ne $data} { if {$data2 ne $data} {
puts stdout "updated template file: $fpath" puts stdout "updated template file: $fpath"
set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data2; close $fdout set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data2; close $fdout
} }
} else { } else {
puts stderr "warning: Missing template file $fpath" puts stderr "warning: Missing template file $fpath"
@ -464,7 +464,7 @@ namespace eval punk::mix::commandset::project {
if {[file exists $projectdir/src/modules]} { if {[file exists $projectdir/src/modules]} {
foreach m $opt_modules { foreach m $opt_modules {
#check if mod-ver.tm file or #modpod-mod-ver folder exist #check if mod-ver.tm file or #modpod-mod-ver folder exist
set tmfile $projectdir/src/modules/$m-[punk::mix::util::magic_tm_version].tm set tmfile $projectdir/src/modules/$m-[punk::mix::util::magic_tm_version].tm
set podfile $projectdir/src/modules/#modpod-$m-[punk::mix::util::magic_tm_version]/$m-[punk::mix::util::magic_tm_version].tm set podfile $projectdir/src/modules/#modpod-$m-[punk::mix::util::magic_tm_version]/$m-[punk::mix::util::magic_tm_version].tm
@ -482,7 +482,7 @@ namespace eval punk::mix::commandset::project {
set overwrite_type zip set overwrite_type zip
} else { } else {
set answer [util::askuser "OVERWRITE the src/modules file $tmfile ?? (generally not desirable) Y|N"] set answer [util::askuser "OVERWRITE the src/modules file $tmfile ?? (generally not desirable) Y|N"]
set overwrite_type $opt_type set overwrite_type $opt_type
} }
if {[string tolower $answer] eq "y"} { if {[string tolower $answer] eq "y"} {
#REVIEW - all pods zip - for now #REVIEW - all pods zip - for now
@ -503,7 +503,7 @@ namespace eval punk::mix::commandset::project {
$installer set_source_target $projectdir/src/doc $projectdir/src/embedded $installer set_source_target $projectdir/src/doc $projectdir/src/embedded
set event [$installer start_event {-install_step kettledoc}] set event [$installer start_event {-install_step kettledoc}]
$event targetset_init VIRTUAL kettle_build_doc ;#VIRTUAL - since there is no specific target file - and we don't know all the files that will be generated $event targetset_init VIRTUAL kettle_build_doc ;#VIRTUAL - since there is no specific target file - and we don't know all the files that will be generated
$event targetset_addsource $projectdir/src/doc ;#whole doc tree is considered the source $event targetset_addsource $projectdir/src/doc ;#whole doc tree is considered the source
#---------- #----------
if {\ if {\
[llength [dict get [$event targetset_source_changes] changed]]\ [llength [dict get [$event targetset_source_changes] changed]]\
@ -535,7 +535,7 @@ namespace eval punk::mix::commandset::project {
if {![punk::repo::is_fossil_root $projectdir]} { if {![punk::repo::is_fossil_root $projectdir]} {
set first_fossil 1 set first_fossil 1
#-k = keep. (only modify the manifest file(s)) #-k = keep. (only modify the manifest file(s))
if {$is_nested_fossil} { if {$is_nested_fossil} {
set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir] set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir]
} else { } else {
@ -600,11 +600,11 @@ namespace eval punk::mix::commandset::project {
#[para]The glob argument is optional unless option/value pairs are also supplied, in which case * should be explicitly supplied #[para]The glob argument is optional unless option/value pairs are also supplied, in which case * should be explicitly supplied
#[para]glob restricts output based on the name of the fossil db file e.g s* for all projects beginning with s #[para]glob restricts output based on the name of the fossil db file e.g s* for all projects beginning with s
#[para]The _default function is made available in the ensemble by the name of the prefix used when importing the commandset. #[para]The _default function is made available in the ensemble by the name of the prefix used when importing the commandset.
#[para]e.g #[para]e.g
#[para] punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection #[para] punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
#[para]Will result in the command being available as <ensemblecommand> projects #[para]Will result in the command being available as <ensemblecommand> projects
package require overtype package require overtype
set db_projects [lib::get_projects $glob] set db_projects [lib::get_projects $glob]
set col1items [lsearch -all -inline -index 0 -subindices $db_projects *] set col1items [lsearch -all -inline -index 0 -subindices $db_projects *]
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] set col2items [lsearch -all -inline -index 1 -subindices $db_projects *]
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *]
@ -620,15 +620,15 @@ namespace eval punk::mix::commandset::project {
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]] set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]]
set col3 [string repeat " " $widest3] set col3 [string repeat " " $widest3]
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}] set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}]
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n
append msg [string repeat "=" $tablewidth] \n append msg [string repeat "=" $tablewidth] \n
foreach p $col1items n $col2items c $col3items { foreach p $col1items n $col2items c $col3items {
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n
} }
return $msg return $msg
#return [list_as_lines [lib::get_projects $glob]] #return [list_as_lines [lib::get_projects $glob]]
} }
proc detail {{glob {}} args} { proc detail {{glob {}} args} {
package require overtype package require overtype
@ -640,14 +640,14 @@ namespace eval punk::mix::commandset::project {
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_description [dict get $opts -description] set opt_description [dict get $opts -description]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set db_projects [lib::get_projects $glob]
set db_projects [lib::get_projects $glob]
set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *] set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *]
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] set col2items [lsearch -all -inline -index 1 -subindices $db_projects *]
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *]
set col3items [lmap v $checkouts {llength $v}] set col3items [lmap v $checkouts {llength $v}]
set col4_pnames [list] set col4_pnames [list]
set col5_pcodes [list] set col5_pcodes [list]
set col6_dupids [list] set col6_dupids [list]
@ -658,13 +658,13 @@ namespace eval punk::mix::commandset::project {
set project_name "" set project_name ""
set project_code "" set project_code ""
set project_desc "" set project_desc ""
set db_error "" set db_error ""
if {[file exists $dbfile]} { if {[file exists $dbfile]} {
if {[catch { if {[catch {
sqlite3 dbp $dbfile sqlite3 dbp $dbfile
dbp eval {select name,value from config where name like 'project-%';} r { dbp eval {select name,value from config where name like 'project-%';} r {
if {$r(name) eq "project-name"} { if {$r(name) eq "project-name"} {
set project_name $r(value) set project_name $r(value)
} elseif {$r(name) eq "project-code"} { } elseif {$r(name) eq "project-code"} {
set project_code $r(value) set project_code $r(value)
} elseif {$r(name) eq "project-description"} { } elseif {$r(name) eq "project-description"} {
@ -687,7 +687,7 @@ namespace eval punk::mix::commandset::project {
} }
incr file_idx incr file_idx
} }
set setid 1 set setid 1
set codeset [dict create] set codeset [dict create]
dict for {code dbs} $codes { dict for {code dbs} $codes {
@ -696,17 +696,17 @@ namespace eval punk::mix::commandset::project {
dict set codeset $code count [llength $dbs] dict set codeset $code count [llength $dbs]
dict set codeset $code seen 0 dict set codeset $code seen 0
incr setid incr setid
} }
} }
set dupid 1 set dupid 1
foreach pc $col5_pcodes { foreach pc $col5_pcodes {
if {[dict exists $codeset $pc]} { if {[dict exists $codeset $pc]} {
set seen [dict get $codeset $pc seen] set seen [dict get $codeset $pc seen]
set this_seen [expr {$seen + 1}] set this_seen [expr {$seen + 1}]
dict set codeset $pc seen $this_seen dict set codeset $pc seen $this_seen
lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]" lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]"
} else { } else {
lappend col6_dupids "" lappend col6_dupids ""
} }
} }
@ -732,10 +732,10 @@ namespace eval punk::mix::commandset::project {
#set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {string length $v}]] #set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {string length $v}]]
set widest7 35 set widest7 35
set col7 [string repeat " " $widest7] set col7 [string repeat " " $widest7]
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}] set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}]
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\ append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\
[overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]" [overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]"
if {!$opt_description} { if {!$opt_description} {
@ -747,7 +747,7 @@ namespace eval punk::mix::commandset::project {
append msg [string repeat "=" $tablewidth] \n append msg [string repeat "=" $tablewidth] \n
foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs { foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs {
set desclines [split [textutil::adjust $desc -length $widest7] \n] set desclines [split [textutil::adjust $desc -length $widest7] \n]
set desc1 [lindex $desclines 0] set desc1 [lindex $desclines 0]
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\ append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\
[overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]" [overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]"
@ -756,20 +756,20 @@ namespace eval punk::mix::commandset::project {
} else { } else {
append msg " [overtype::left $col7 $desc1]" \n append msg " [overtype::left $col7 $desc1]" \n
foreach dline [lrange $desclines 1 end] { foreach dline [lrange $desclines 1 end] {
append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n
} }
} }
} }
return $msg return $msg
#return [list_as_lines [lib::get_projects $glob]] #return [list_as_lines [lib::get_projects $glob]]
} }
proc cd {{glob {}} args} { proc cd {{glob {}} args} {
dict set args -cd 1 dict set args -cd 1
work $glob {*}$args work $glob {*}$args
} }
proc work {{glob {}} args} { proc work {{glob {}} args} {
package require sqlite3 package require sqlite3
set db_projects [lib::get_projects $glob] set db_projects [lib::get_projects $glob]
if {[llength $db_projects] == 0} { if {[llength $db_projects] == 0} {
puts stderr "::punk::mix::commandset::project::work No Repo DB name matches found for '$glob'" puts stderr "::punk::mix::commandset::project::work No Repo DB name matches found for '$glob'"
return "" return ""
@ -779,22 +779,22 @@ namespace eval punk::mix::commandset::project {
set defaults [dict create\ set defaults [dict create\
-cd 0\ -cd 0\
-detail "\uFFFF"\ -detail "\uFFFF"\
] ]
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_cd [dict get $opts -cd] set opt_cd [dict get $opts -cd]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_detail [dict get $opts -detail] set opt_detail [dict get $opts -detail]
set opt_detail_explicit_zero 1 ;#default assumption only set opt_detail_explicit_zero 1 ;#default assumption only
if {$opt_detail eq "\uFFFF"} { if {$opt_detail eq "\uFFFF"} {
set opt_detail_explicit_zero 0 set opt_detail_explicit_zero 0
set opt_detail 0; #default set opt_detail 0; #default
} }
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set workdir_dict [dict create] set workdir_dict [dict create]
set all_workdirs [list] set all_workdirs [list]
foreach pinfo $db_projects { foreach pinfo $db_projects {
lassign $pinfo fosdb name workdirs lassign $pinfo fosdb name workdirs
foreach wdir $workdirs { foreach wdir $workdirs {
dict set workdir_dict $wdir $pinfo dict set workdir_dict $wdir $pinfo
lappend all_workdirs $wdir lappend all_workdirs $wdir
@ -808,15 +808,15 @@ namespace eval punk::mix::commandset::project {
set col_pcodes [list] set col_pcodes [list]
set col_dupids [list] set col_dupids [list]
set fosdb_count [dict create] set fosdb_count [dict create]
set fosdb_dupset [dict create] set fosdb_dupset [dict create]
set fosdb_cache [dict create] set fosdb_cache [dict create]
set dupset 0 set dupset 0
set rowid 1 set rowid 1
foreach wd $workdirs { foreach wd $workdirs {
set wdinfo [dict get $workdir_dict $wd] set wdinfo [dict get $workdir_dict $wd]
lassign $wdinfo fosdb nm siblingworkdirs lassign $wdinfo fosdb nm siblingworkdirs
dict incr fosdb_count $fosdb dict incr fosdb_count $fosdb
set dbcount [dict get $fosdb_count $fosdb] set dbcount [dict get $fosdb_count $fosdb]
if {[llength $siblingworkdirs] > 1} { if {[llength $siblingworkdirs] > 1} {
if {![dict exists $fosdb_dupset $fosdb]} { if {![dict exists $fosdb_dupset $fosdb]} {
@ -825,7 +825,7 @@ namespace eval punk::mix::commandset::project {
} }
set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]" set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]"
} else { } else {
set dupid "" set dupid ""
} }
if {$dbcount == 1} { if {$dbcount == 1} {
set pname "" set pname ""
@ -842,7 +842,7 @@ namespace eval punk::mix::commandset::project {
puts stderr "!!! error: $errM" puts stderr "!!! error: $errM"
} }
} else { } else {
puts stderr "!!! missing fossil db $fosdb" puts stderr "!!! missing fossil db $fosdb"
} }
} else { } else {
set info [dict get $fosdb_cache $fosdb] set info [dict get $fosdb_cache $fosdb]
@ -858,7 +858,7 @@ namespace eval punk::mix::commandset::project {
set col_states [list] set col_states [list]
set state_title "" set state_title ""
#if only one set of fossil checkouts in the resultset and opt_detail is 0 and not explicit - retrieve workingdir state for each co #if only one set of fossil checkouts in the resultset and opt_detail is 0 and not explicit - retrieve workingdir state for each co
if {([llength [dict keys $fosdb_cache]] == 1)} { if {([llength [dict keys $fosdb_cache]] == 1)} {
if {!$opt_detail_explicit_zero} { if {!$opt_detail_explicit_zero} {
set opt_detail 1 set opt_detail 1
@ -884,13 +884,13 @@ namespace eval punk::mix::commandset::project {
set state_dict [punk::repo::workingdir_state_summary_dict $wd_state] set state_dict [punk::repo::workingdir_state_summary_dict $wd_state]
lappend c_rev [string range [dict get $state_dict revision] 0 9] lappend c_rev [string range [dict get $state_dict revision] 0 9]
lappend c_rev_iso [dict get $state_dict revision_iso8601] lappend c_rev_iso [dict get $state_dict revision_iso8601]
lappend c_unchanged [dict get $state_dict unchanged] lappend c_unchanged [dict get $state_dict unchanged]
lappend c_changed [dict get $state_dict changed] lappend c_changed [dict get $state_dict changed]
lappend c_new [dict get $state_dict new] lappend c_new [dict get $state_dict new]
lappend c_missing [dict get $state_dict missing] lappend c_missing [dict get $state_dict missing]
lappend c_extra [dict get $state_dict extra] lappend c_extra [dict get $state_dict extra]
puts -nonewline stderr "." puts -nonewline stderr "."
} }
puts -nonewline stderr \n puts -nonewline stderr \n
set t0 "Revision" set t0 "Revision"
set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]] set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]]
@ -913,13 +913,13 @@ namespace eval punk::mix::commandset::project {
set t5 "Extr" set t5 "Extr"
set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]] set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]]
set c5 [string repeat " " $w5] set c5 [string repeat " " $w5]
set state_title "[overtype::left $c0 $t0] [overtype::left $c0b $t0b] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]" set state_title "[overtype::left $c0 $t0] [overtype::left $c0b $t0b] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]"
foreach r $c_rev iso $c_rev_iso u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra { foreach r $c_rev iso $c_rev_iso u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra {
lappend col_states "[overtype::left $c0 $r] [overtype::left $c0b $iso] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]" lappend col_states "[overtype::left $c0 $r] [overtype::left $c0b $iso] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]"
} }
} }
set msg "" set msg ""
if {$opt_cd} { if {$opt_cd} {
set title0 "CD" set title0 "CD"
@ -948,7 +948,7 @@ namespace eval punk::mix::commandset::project {
append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]" append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]"
if {[llength $col_states]} { if {[llength $col_states]} {
set title6 $state_title set title6 $state_title
set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]] set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]]
set col6 [string repeat " " $widest6] set col6 [string repeat " " $widest6]
incr tablewidth [expr {$widest6 + 1}] incr tablewidth [expr {$widest6 + 1}]
@ -965,7 +965,7 @@ namespace eval punk::mix::commandset::project {
set wd [punk::ansi::a+ red]$wd[a] set wd [punk::ansi::a+ red]$wd[a]
} }
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} { if {![file exists $wd]} {
@ -973,7 +973,7 @@ namespace eval punk::mix::commandset::project {
set wd [punk::ansi::a+ red]$wd[a] set wd [punk::ansi::a+ red]$wd[a]
} }
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }
set numrows [llength $col_rowids] set numrows [llength $col_rowids]
if {$opt_cd && $numrows >= 1} { if {$opt_cd && $numrows >= 1} {
@ -985,7 +985,7 @@ namespace eval punk::mix::commandset::project {
::cd $workingdir ::cd $workingdir
return $workingdir return $workingdir
} else { } else {
puts stderr "path $workingdir doesn't appear to exist" puts stderr "path $workingdir doesn't appear to exist"
return [pwd] return [pwd]
} }
} else { } else {
@ -1004,12 +1004,12 @@ namespace eval punk::mix::commandset::project {
#*** !doctools #*** !doctools
#[list_end] [comment {-- end collection namespace definitions --}] #[list_end] [comment {-- end collection namespace definitions --}]
} }
namespace eval lib { namespace eval lib {
proc template_tag {tagname} { proc template_tag {tagname} {
#todo - support different tagwrappers - it shouldn't be so likely to collide with common code idioms etc. #todo - support different tagwrappers - it shouldn't be so likely to collide with common code idioms etc.
#we need to detect presence of tags intended for punk::mix system #we need to detect presence of tags intended for punk::mix system
#consider using punk::cap to enable multiple template-substitution providers with their own set of tagnames and/or tag wrappers, where substitution providers are all run #consider using punk::cap to enable multiple template-substitution providers with their own set of tagnames and/or tag wrappers, where substitution providers are all run
return [string cat % $tagname %] return [string cat % $tagname %]
} }
#get project info only by opening the central confg-db #get project info only by opening the central confg-db
@ -1032,12 +1032,13 @@ namespace eval punk::mix::commandset::project {
set path [string trim [string range $pr 5 end]] set path [string trim [string range $pr 5 end]]
set nm [file rootname [file tail $path]] set nm [file rootname [file tail $path]]
set ckouts [fosconf eval {select name from global_config where value = $path;}] set ckouts [fosconf eval {select name from global_config where value = $path;}]
#list of entries like "ckout:C:/buildtcl/2024zig/tcl90/"
set checkout_paths [list] set checkout_paths [list]
#strip "ckout:" #strip "ckout:"
foreach ck $ckouts { foreach ck $ckouts {
lappend checkout_paths [string trim [string range $ck 6 end]] lappend checkout_paths [string trim [string range $ck 6 end]]
} }
lappend paths_and_names [list $path $nm $checkout_paths] lappend paths_and_names [list $path $nm $checkout_paths]
} }
set filtered_list [list] set filtered_list [list]
foreach glob $globlist { foreach glob $globlist {
@ -1045,16 +1046,14 @@ namespace eval punk::mix::commandset::project {
foreach m $matches { foreach m $matches {
if {$m ni $filtered_list} { if {$m ni $filtered_list} {
lappend filtered_list $m lappend filtered_list $m
} }
} }
} }
set projects [lsort -index 1 $filtered_list] set projects [lsort -index 1 $filtered_list]
return $projects return $projects
} }
} }
@ -1067,15 +1066,10 @@ namespace eval punk::mix::commandset::project {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project { package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

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

@ -24,6 +24,9 @@
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::mix::commandset::repo { namespace eval punk::mix::commandset::repo {
namespace export * namespace export *
variable PUNKARGS
proc tickets {{project ""}} { proc tickets {{project ""}} {
#todo #todo
set result "" set result ""
@ -52,9 +55,9 @@ namespace eval punk::mix::commandset::repo {
set repopaths [punk::repo::find_repos [pwd]] set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos] set repos [dict get $repopaths repos]
if {![llength $repos]} { if {![llength $repos]} {
append result [dict get $repopaths warnings] append result [a+ bold yellow][dict get $repopaths warnings][a]
} else { } else {
append result [dict get $repopaths warnings] append result [a+ bold yellow][dict get $repopaths warnings][a]
lassign [lindex $repos 0] repopath repotypes lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} { if {"fossil" in $repotypes} {
append result \n "Fossil repo based at $repopath" append result \n "Fossil repo based at $repopath"
@ -69,6 +72,17 @@ namespace eval punk::mix::commandset::repo {
} }
return $result return $result
} }
#punk::args
lappend PUNKARGS [list {
@id -id ::punk::mix::commandset::repo::fossil-move-repository
@cmd -name punk::mix::commandset::repo::fossil-move-repository -help\
"Move the fossil repository file (usually named with .fossil extension).
This is an interactive function which will prompt for answers on stdin
before proceeding.
The move can be done even if there are open checkouts and will maintain
the link between checkout databases and the repository file."
}]
proc fossil-move-repository {{path ""}} { proc fossil-move-repository {{path ""}} {
set searchbase [pwd] set searchbase [pwd]
set projectinfo [punk::repo::find_repos $searchbase] set projectinfo [punk::repo::find_repos $searchbase]
@ -281,7 +295,7 @@ namespace eval punk::mix::commandset::repo {
set ckouts [oldrepo eval {select name from config where name like 'ckout:%'}] set ckouts [oldrepo eval {select name from config where name like 'ckout:%'}]
oldrepo close oldrepo close
if {[llength $ckouts] > 1} { if {[llength $ckouts] > 1} {
puts stdout "There are [llength $ckouts] checkouts for the repository you are moving" puts stdout "There are [llength $ckouts] checkouts for the repository you are moving"
puts stdout "You will be asked for each checkout if you want to adjust it to point to $target_repodb_folder/$pname2.folder" puts stdout "You will be asked for each checkout if you want to adjust it to point to $target_repodb_folder/$pname2.folder"
} }
set original_cwd [pwd] set original_cwd [pwd]
@ -304,11 +318,11 @@ namespace eval punk::mix::commandset::repo {
puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}" puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}"
puts stderr "$moveresult" puts stderr "$moveresult"
} else { } else {
puts stdout "OK - move performed with result:" puts stdout "OK - move performed with result:"
puts stdout $moveresult puts stdout $moveresult
} }
} }
} }
cd $original_cwd cd $original_cwd
} }
@ -379,7 +393,7 @@ namespace eval punk::mix::commandset::repo {
puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}" puts stderr "${ansiwarn}The fossil test-move-repository command appears to have failed${ansireset}"
puts stderr "$moveresult" puts stderr "$moveresult"
} else { } else {
puts stdout "OK - move performed with result:" puts stdout "OK - move performed with result:"
puts stdout $moveresult puts stdout $moveresult
} }
} }
@ -402,10 +416,10 @@ namespace eval punk::mix::commandset::repo {
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::mix::commandset::repo
}
@ -413,9 +427,9 @@ namespace eval punk::mix::commandset::repo {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::repo [namespace eval punk::mix::commandset::repo { package provide punk::mix::commandset::repo [namespace eval punk::mix::commandset::repo {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

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

@ -1,164 +1,163 @@
#punkapps app manager #punkapps app manager
# deck cli # deck cli
namespace eval punk::mod::cli { namespace eval punk::mod::cli {
namespace export help list run namespace export help list run
namespace ensemble create namespace ensemble create
# namespace ensemble configure [namespace current] -unknown punk::mod::cli::_unknown # namespace ensemble configure [namespace current] -unknown punk::mod::cli::_unknown
if 0 { if 0 {
proc _unknown {ns args} { proc _unknown {ns args} {
puts stderr "punk::mod::cli::_unknown '$ns' '$args'" puts stderr "punk::mod::cli::_unknown '$ns' '$args'"
puts stderr "punk::mod::cli::help $args" puts stderr "punk::mod::cli::help $args"
puts stderr "arglen:[llength $args]" puts stderr "arglen:[llength $args]"
punk::mod::cli::help {*}$args punk::mod::cli::help {*}$args
} }
} }
#cli must have _init method - usually used to load commandsets lazily #cli must have _init method - usually used to load commandsets lazily
# #
variable initialised 0 variable initialised 0
proc _init {args} { proc _init {args} {
variable initialised variable initialised
if {$initialised} { if {$initialised} {
return return
} }
#... #...
set initialised 1 set initialised 1
} }
proc help {args} { proc help {args} {
set basehelp [punk::mix::base help {*}$args] set basehelp [punk::mix::base help {*}$args]
#namespace export #namespace export
return $basehelp return $basehelp
} }
proc getraw {appname} { proc getraw {appname} {
upvar ::punk::config::running running_config upvar ::punk::config::running running_config
set app_folders [dict get $running_config apps] set app_folders [dict get $running_config apps]
#todo search each app folder #todo search each app folder
set bases [::list] set bases [::list]
set versions [::list] set versions [::list]
set mains [::list] set mains [::list]
set appinfo [::list bases {} mains {} versions {}] set appinfo [::list bases {} mains {} versions {}]
foreach containerfolder $app_folders { foreach containerfolder $app_folders {
lappend bases $containerfolder lappend bases $containerfolder
if {[file exists $containerfolder]} { if {[file exists $containerfolder]} {
if {[file exists $containerfolder/$appname/main.tcl]} { if {[file exists $containerfolder/$appname/main.tcl]} {
#exact match - only return info for the exact one specified #exact match - only return info for the exact one specified
set namematches $appname set namematches $appname
set parts [split $appname -] set parts [split $appname -]
} else { } else {
set namematches [glob -nocomplain -dir $containerfolder -type d -tail ${appname}-*] set namematches [glob -nocomplain -dir $containerfolder -type d -tail ${appname}-*]
set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort? set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort?
} }
foreach nm $namematches { foreach nm $namematches {
set mainfile $containerfolder/$nm/main.tcl set mainfile $containerfolder/$nm/main.tcl
set parts [split $nm -] set parts [split $nm -]
if {[llength $parts] == 1} { if {[llength $parts] == 1} {
set ver "" set ver ""
} else { } else {
set ver [lindex $parts end] set ver [lindex $parts end]
} }
if {$ver ni $versions} { if {$ver ni $versions} {
lappend versions $ver lappend versions $ver
lappend mains $ver $mainfile lappend mains $ver $mainfile
} else { } else {
puts stderr "punk::apps::app version '$ver' of app '$appname' already encountered at $mainfile. (will use earliest encountered in running-config apps and ignore others of same version)" puts stderr "punk::apps::app version '$ver' of app '$appname' already encountered at $mainfile. (will use earliest encountered in running-config apps and ignore others of same version)"
} }
} }
} else { } else {
puts stderr "punk::apps::app missing apps_folder:'$containerfolder' Ensure apps_folder is set in punk::config" puts stderr "punk::apps::app missing apps_folder:'$containerfolder' Ensure apps_folder is set in punk::config"
} }
} }
dict set appinfo versions $versions dict set appinfo versions $versions
#todo - natsort! #todo - natsort!
set sorted_versions [lsort $versions] set sorted_versions [lsort $versions]
set latest [lindex $sorted_versions 0] set latest [lindex $sorted_versions 0]
if {$latest eq "" && [llength $sorted_versions] > 1} { if {$latest eq "" && [llength $sorted_versions] > 1} {
set latest [lindex $sorted_versions 1 set latest [lindex $sorted_versions 1]
} }
dict set appinfo latest $latest dict set appinfo latest $latest
dict set appinfo bases $bases dict set appinfo bases $bases
dict set appinfo mains $mains dict set appinfo mains $mains
return $appinfo return $appinfo
} }
proc list {{glob *}} { proc list {{glob *}} {
upvar ::punk::config::running running_config upvar ::punk::config::running running_config
set apps_folder [dict get $running_config apps] set apps_folder [dict get $running_config apps]
if {[file exists $apps_folder]} { if {[file exists $apps_folder]} {
if {[file exists $apps_folder/$glob]} { if {[file exists $apps_folder/$glob]} {
#tailcall source $apps_folder/$glob/main.tcl #tailcall source $apps_folder/$glob/main.tcl
return $glob return $glob
} }
set apps [glob -nocomplain -dir $apps_folder -type d -tail $glob] set apps [glob -nocomplain -dir $apps_folder -type d -tail $glob]
if {[llength $apps] == 0} { if {[llength $apps] == 0} {
if {[string first * $glob] <0 && [string first ? $glob] <0} { if {[string first * $glob] <0 && [string first ? $glob] <0} {
#no glob chars supplied - only launch if exact match for name part #no glob chars supplied - only launch if exact match for name part
set namematches [glob -nocomplain -dir $apps_folder -type d -tail ${glob}-*] set namematches [glob -nocomplain -dir $apps_folder -type d -tail ${glob}-*]
set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort? set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort?
if {[llength $namematches] > 0} { if {[llength $namematches] > 0} {
set latest [lindex $namematches end] set latest [lindex $namematches end]
lassign $latest nm ver lassign $latest nm ver
#tailcall source $apps_folder/$latest/main.tcl #tailcall source $apps_folder/$latest/main.tcl
} }
} }
} }
return $apps return $apps
} }
} }
#todo - way to launch as separate process #todo - way to launch as separate process
# solo-opts only before appname - args following appname are passed to the app # solo-opts only before appname - args following appname are passed to the app
proc run {args} { proc run {args} {
set nameposn [lsearch -not $args -*] set nameposn [lsearch -not $args -*]
if {$nameposn < 0} { if {$nameposn < 0} {
error "punkapp::run unable to determine application name" error "punkapp::run unable to determine application name"
} }
set appname [lindex $args $nameposn] set appname [lindex $args $nameposn]
set controlargs [lrange $args 0 $nameposn-1] set controlargs [lrange $args 0 $nameposn-1]
set appargs [lrange $args $nameposn+1 end] set appargs [lrange $args $nameposn+1 end]
set appinfo [punk::mod::cli::getraw $appname] set appinfo [punk::mod::cli::getraw $appname]
if {[llength [dict get $appinfo versions]]} { if {[llength [dict get $appinfo versions]]} {
set ver [dict get $appinfo latest] set ver [dict get $appinfo latest]
puts stdout "info: $appinfo" puts stdout "info: $appinfo"
set ::argc [llength $appargs] set ::argc [llength $appargs]
set ::argv $appargs set ::argv $appargs
source [dict get $appinfo mains $ver] source [dict get $appinfo mains $ver]
if {"-hideconsole" in $controlargs} { if {"-hideconsole" in $controlargs} {
puts stderr "attempting console hide" puts stderr "attempting console hide"
#todo - something better - a callback when window mapped? #todo - something better - a callback when window mapped?
after 500 {::punkapp::hide_console} after 500 {::punkapp::hide_console}
} }
return $appinfo return $appinfo
} else { } else {
error "punk::mod::cli unable to run '$appname'. main.tcl not found in [dict get $appinfo bases]" error "punk::mod::cli unable to run '$appname'. main.tcl not found in [dict get $appinfo bases]"
} }
} }
} }
namespace eval punk::mod::cli { namespace eval punk::mod::cli {
proc _cli {args} { proc _cli {args} {
#don't use tailcall - base uses info level to determine caller #don't use tailcall - base uses info level to determine caller
::punk::mix::base::_cli {*}$args ::punk::mix::base::_cli {*}$args
} }
variable default_command help variable default_command help
package require punk::mix::base package require punk::mix::base
package require punk::overlay package require punk::overlay
punk::overlay::custom_from_base [namespace current] ::punk::mix::base punk::overlay::custom_from_base [namespace current] ::punk::mix::base
} }
package provide punk::mod [namespace eval punk::mod { package provide punk::mod [namespace eval punk::mod {
variable version variable version
set version 0.1 set version 0.1
}]
}]

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

@ -657,6 +657,7 @@ namespace eval punk::path {
**/_aside (exlude files where _aside is last segment) **/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder) **/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)" **/_aside/** (exclude all folders with _aside as a segment)"
-antiglob_files -default {}
@values -min 0 -max -1 -optional 1 -type string @values -min 0 -max -1 -optional 1 -type string
tailglobs -default * -multiple 1 -help\ tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path "Patterns to match against filename portion (last segment) of each file path
@ -681,6 +682,7 @@ namespace eval punk::path {
set tailglobs [dict get $values tailglobs] set tailglobs [dict get $values tailglobs]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths] set opt_antiglob_paths [dict get $opts -antiglob_paths]
set opt_antiglob_files [dict get $opts -antiglob_files]
set CALLDEPTH [dict get $opts -call-depth-internal] set CALLDEPTH [dict get $opts -call-depth-internal]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
@ -718,7 +720,24 @@ namespace eval punk::path {
puts stderr "treefilenames error while listing files in dir $opt_dir\n $matches" puts stderr "treefilenames error while listing files in dir $opt_dir\n $matches"
set dirfiles [list] set dirfiles [list]
} else { } else {
set dirfiles [lsort $matches] set retained [list]
if {[llength $opt_antiglob_files]} {
foreach m $matches {
set skip 0
set ftail [file tail $m]
foreach anti $opt_antiglob_files {
if {[string match $anti $ftail]} {
set skip 1; break
}
}
if {!$skip} {
lappend retained $m
}
}
} else {
set retained $matches
}
set dirfiles [lsort $retained]
} }
lappend files {*}$dirfiles lappend files {*}$dirfiles

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

@ -39,16 +39,16 @@ if {$::tcl_platform(platform) eq "windows"} {
} }
package require fileutil; #tcllib package require fileutil; #tcllib
package require punk::path package require punk::path
package require punk::mix::base ;#uses core functions from punk::mix::base::lib namespace e.g cksum_path package require punk::mix::base ;#uses core functions from punk::mix::base::lib namespace e.g cksum_path
package require punk::mix::util ;#do_in_path package require punk::mix::util ;#do_in_path
# -- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- ---
# For performance/efficiency reasons - use file functions on paths in preference to string operations # For performance/efficiency reasons - use file functions on paths in preference to string operations
# e.g use file join # e.g use file join
# branch to avoid unnecessary calls to 'pwd' or 'file normalize' - which can be surprisingly expensive operations (as at tcl 8.7 2023) # branch to avoid unnecessary calls to 'pwd' or 'file normalize' - which can be surprisingly expensive operations (as at tcl 8.7 2023)
# pwd is only expensive if we treat it as a string instead of a list/path # pwd is only expensive if we treat it as a string instead of a list/path
# e.g # e.g
# > time {set x [pwd]} # > time {set x [pwd]}
# 5 microsoeconds.. no problem # 5 microsoeconds.. no problem
# > time {set x [pwd]} # > time {set x [pwd]}
@ -67,11 +67,11 @@ namespace eval punk::repo {
variable cached_command_paths variable cached_command_paths
set cached_command_paths [dict create] set cached_command_paths [dict create]
#anticipating possible removal of buggy caching from auto_execok #anticipating possible removal of buggy caching from auto_execok
#mentioned in: https://core.tcl-lang.org/tcl/tktview/4dc35e0c0c #mentioned in: https://core.tcl-lang.org/tcl/tktview/4dc35e0c0c
#this would leave the application to decide what it wants to cache in that regard. #this would leave the application to decide what it wants to cache in that regard.
proc Cached_auto_execok {name} { proc Cached_auto_execok {name} {
return [auto_execok $name] return [auto_execok $name]
#variable cached_command_paths #variable cached_command_paths
#if {[dict exists $cached_command_paths $name]} { #if {[dict exists $cached_command_paths $name]} {
# return [dict get $cached_command_paths $name] # return [dict get $cached_command_paths $name]
@ -102,14 +102,14 @@ namespace eval punk::repo {
"" {${$othercmds}} "" {${$othercmds}}
} }
}] }]
return $result return $result
} }
#lappend PUNKARGS [list { #lappend PUNKARGS [list {
# @dynamic # @dynamic
# @id -id ::punk::repo::fossil_proxy # @id -id ::punk::repo::fossil_proxy
# @cmd -name fossil -help "fossil executable # @cmd -name fossil -help "fossil executable
# " # "
# @argdisplay -header "fossil help" -body {${[runout -n fossil help]}} # @argdisplay -header "fossil help" -body {${[runout -n fossil help]}}
@ -117,7 +117,7 @@ namespace eval punk::repo {
lappend PUNKARGS [list { lappend PUNKARGS [list {
@dynamic @dynamic
@id -id ::punk::repo::fossil_proxy @id -id ::punk::repo::fossil_proxy
@cmd -name fossil -help "fossil executable" @cmd -name fossil -help "fossil executable"
${[punk::repo::get_fossil_usage]} ${[punk::repo::get_fossil_usage]}
} ] } ]
@ -128,14 +128,13 @@ namespace eval punk::repo {
lappend PUNKARGS [list { lappend PUNKARGS [list {
@dynamic @dynamic
@id -id "::punk::repo::fossil_proxy diff" @id -id "::punk::repo::fossil_proxy diff"
@cmd -name "fossil diff" -help "fossil diff @cmd -name "fossil diff" -help "fossil diff"
"
@argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}} @argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}}
} ""] } ""]
lappend PUNKARGS [list { lappend PUNKARGS [list {
#todo - remove this comment - testing dynamic directive #todo - remove this comment - testing dynamic directive
@dynamic @dynamic
@id -id "::punk::repo::fossil_proxy add" @id -id "::punk::repo::fossil_proxy add"
@cmd -name "fossil add" -help "fossil add @cmd -name "fossil add" -help "fossil add
" "
@argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}} @argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
@ -152,16 +151,16 @@ namespace eval punk::repo {
lappend PUNKARGS_aliases {"::fossil diff" "::punk::repo::fossil_proxy diff"} lappend PUNKARGS_aliases {"::fossil diff" "::punk::repo::fossil_proxy diff"}
#Todo - investigate proper way to install a client-side commit hook in the fossil project #Todo - investigate proper way to install a client-side commit hook in the fossil project
#Then we may still use this proxy to check the hook - but the required checks will occur when another shell used #Then we may still use this proxy to check the hook - but the required checks will occur when another shell used
proc fossil_proxy {args} { proc fossil_proxy {args} {
set start_dir [pwd] set start_dir [pwd]
set fosroot [find_fossil $start_dir] set fosroot [find_fossil $start_dir]
set fossilcmd [lindex $args 0] set fossilcmd [lindex $args 0]
set no_warning_commands [list "help" "dbstat" "grep" "diff" "xdiff" "cat" "version"] set no_warning_commands [list "help" "dbstat" "grep" "diff" "xdiff" "cat" "version"]
if {$fossilcmd ni $no_warning_commands } { if {$fossilcmd ni $no_warning_commands } {
set repostate [find_repos $start_dir] set repostate [find_repos $start_dir]
} }
set no_prompt_commands [list "status" "info" {*}$no_warning_commands] set no_prompt_commands [list "status" "info" {*}$no_warning_commands]
@ -170,7 +169,7 @@ namespace eval punk::repo {
if {$fossilcmd ni $no_prompt_commands} { if {$fossilcmd ni $no_prompt_commands} {
set fossilrepos [dict get $repostate fossil] set fossilrepos [dict get $repostate fossil]
if {[llength $fossilrepos] > 1} { if {[llength $fossilrepos] > 1} {
puts stdout [dict get $repostate warnings] puts stdout [punk::ansi::a+ bold yellow][dict get $repostate warnings][punk::ansi::a]
puts stdout "Operating on inner fossil repository: [lindex $fossilrepos 0]" puts stdout "Operating on inner fossil repository: [lindex $fossilrepos 0]"
puts stdout "Use FOSSIL instead of fossil to avoid this prompt and warning" puts stdout "Use FOSSIL instead of fossil to avoid this prompt and warning"
set answer [askuser "Are you sure you want to perform the operation on this repo? Y/N"] set answer [askuser "Are you sure you want to perform the operation on this repo? Y/N"]
@ -217,7 +216,7 @@ namespace eval punk::repo {
} }
} elseif {$fossilcmd in [list "info" "status"]} { } elseif {$fossilcmd in [list "info" "status"]} {
#emit warning whether or not multiple fossil repos #emit warning whether or not multiple fossil repos
puts stdout [dict get $repostate warnings] puts stdout [punk::ansi::a+ bold yellow][dict get $repostate warnings][punk::ansi::a]
} }
set fossil_prog [Cached_auto_execok fossil] set fossil_prog [Cached_auto_execok fossil]
if {$fossil_prog ne ""} { if {$fossil_prog ne ""} {
@ -234,7 +233,7 @@ namespace eval punk::repo {
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
#catch { #catch {
# if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
# interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
# } # }
#} #}
@ -245,7 +244,7 @@ namespace eval punk::repo {
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy #uppercase FOSSIL to bypass fossil as alias to fossil_proxy
#only necessary on unix? #only necessary on unix?
#Windows filesystem case insensitive so any non-lowercase fossil version goes out to get an ::auto_execs entry anyway #Windows filesystem case insensitive so any non-lowercase fossil version goes out to get an ::auto_execs entry anyway
proc establish_FOSSIL {args} { proc establish_FOSSIL {args} {
#review #review
if {![info exists ::auto_execs(FOSSIL)]} { if {![info exists ::auto_execs(FOSSIL)]} {
@ -298,7 +297,7 @@ namespace eval punk::repo {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
scanup $path is_fossil_root scanup $path is_fossil_root
} }
proc find_git {{path {}}} { proc find_git {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
scanup $path is_git_root scanup $path is_git_root
@ -330,12 +329,31 @@ namespace eval punk::repo {
} }
} }
} }
lappend PUNKARGS [list {
@id -id "::punk::repo::find_project"
@cmd -name "punk::repo::find_project" -help\
"Find and return the path for the root of
the project to which the supplied path belongs.
If the supplied path is empty, the current
working directory is used as the starting point
for the upwards search.
Returns nothing if there is no project at or
above the specified path."
@values -min 0 -max 1
path -optional 1 -default "" -help\
"May be an absolute or relative path.
The full specified path doesn't have
to exist. The code will walk upwards
along the segments of the supplied path
testing the result of 'is_project_root'."
}]
proc find_project {{path {}}} { proc find_project {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
scanup $path is_project_root scanup $path is_project_root
} }
proc is_fossil_root {{path {}}} { #detect if path is a fossil root - without consulting fossil databases
proc is_fossil_root2 {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
#from kettle::path::is.fossil #from kettle::path::is.fossil
foreach control { foreach control {
@ -348,20 +366,51 @@ namespace eval punk::repo {
} }
return 0 return 0
} }
proc is_fossil_root {{path {}}} {
#much faster on windows than 'file exists' checks
if {$path eq {}} { set path [pwd] }
set control [list _FOSSIL_ .fslckout .fos]
#could be marked 'hidden' on windows
if {"windows" eq $::tcl_platform(platform)} {
set files [list {*}[glob -nocomplain -dir $path -types f -tail {*}$control] {*}[glob -nocomplain -dir $path -types {f hidden} -tail {*}$control]]
} else {
set files [glob -nocomplain -dir $path -types f -tail {*}$control]
}
expr {[llength $files] > 0}
}
#review - is a .git folder sufficient? #review - is a .git folder sufficient?
#consider git rev-parse --git-dir ? #consider git rev-parse --git-dir ?
proc is_git_root {{path {}}} { proc is_git_root {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
set control [file join $path .git] #set control [file join $path .git]
expr {[file exists $control] && [file isdirectory $control]} #expr {[file exists $control] && [file isdirectory $control]}
if {"windows" eq $::tcl_platform(platform)} {
#:/
#globbing for dotfiles in tcl is problematic across platforms - windows 'hidden' concept is independent
#we need to find .git whether hidden or not - so need 2 glob operations
#.git may or may not be set with windows 'hidden' attribute
set hiddengitdir [glob -nocomplain -dir $path -types {d hidden} -tail .git]
set nonhiddengitdir [glob -nocomplain -dir $path -types {d} -tail .git] ;#won't return hidden :/
return [expr {[llength [list {*}$hiddengitdir {*}$nonhiddengitdir]] > 0}]
} else {
#:/
#unix returns 'hidden' files even without the hidden type being specified - but only if the pattern explicitly matches
return [expr {[llength [glob -nocomplain -dir $path -types d -tail .git]] > 0}] ;#will return .git even though it is conventionally 'hidden' on unix :/
}
} }
proc is_repo_root {{path {}}} { proc is_repo_root {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
expr {[is_fossil_root $path] || [is_git_root $path]} #expr {[is_fossil_root $path] || [is_git_root $path]}
expr {[is_git_root $path] || [is_fossil_root $path]} ;#is_git_root has less to check
} }
#require a minimum of src and src/modules|src/scriptapps|src/*/*.vfs - and that it's otherwise sensible
#we still run a high chance of picking up unintended candidates - but hopefully it's a reasonable balance. #after excluding undesirables;
#require a minimum of
# - (src and src/modules|src/scriptapps|src/vfs)
# - OR (src and punkproject.toml)
# - and that it's otherwise sensible
#we still run a chance of picking up unintended candidates - but hopefully it's a reasonable balance.
proc is_candidate_root {{path {}}} { proc is_candidate_root {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
if {[file pathtype $path] eq "relative"} { if {[file pathtype $path] eq "relative"} {
@ -380,24 +429,34 @@ namespace eval punk::repo {
} }
#review - adjust to allow symlinks to folders? #review - adjust to allow symlinks to folders?
foreach required { #foreach required {
src # src
} { #} {
set req $path/$required # set req $path/$required
if {(![file exists $req]) || ([file type $req] ne "directory") } {return 0} # if {(![file exists $req]) || ([file type $req] ne "directory") } {return 0}
#}
set required [list src]
set found_required [glob -nocomplain -dir $path -types d -tails {*}$required]
if {[llength $found_required] < [llength $required]} {
return 0
} }
set src_subs [glob -nocomplain -dir $path/src -types d -tail *] set src_subs [glob -nocomplain -dir $path/src -types d -tail *]
#test for $path/src/lib is too common to be a useful indicator #test for $path/src/lib is too common to be a useful indicator
if {"modules" in $src_subs || "scriptapps" in $src_subs} { if {"modules" in $src_subs || "vfs" in $src_subs || "scriptapps" in $src_subs} {
#bare minimum 1
return 1 return 1
} }
foreach sub $src_subs {
if {[string match *.vfs $sub]} { #bare minimum2
return 1 # - has src folder and (possibly empty?) punkproject.toml
} if {[file exists $path/punkproject.toml]} {
return 1
} }
#review - do we need to check if path is already within a project?
#can we have a nested project? Seems like asking for complexity and problems when considering possible effects for git/fossil
#todo - breadth first search with depth limit (say depth 3?) for *.tm or *.tcl as another positive qualifier for this dir to be a project-root #todo - breadth first search with depth limit (say depth 3?) for *.tm or *.tcl as another positive qualifier for this dir to be a project-root
#we probably don't want to deep search a src folder in case the user is accidentally in some other type of project's tree #we probably don't want to deep search a src folder in case the user is accidentally in some other type of project's tree
#such a src tree could be very large, so if we don't find tcl indicators near the root it's a good bet this isn't a candidate #such a src tree could be very large, so if we don't find tcl indicators near the root it's a good bet this isn't a candidate
@ -415,14 +474,22 @@ namespace eval punk::repo {
} }
proc is_project_root {path} { proc is_project_root {path} {
#review - find a reliable simple mechanism. Noting we have projects based on different templates. #review - find a reliable simple mechanism. Noting we have projects based on different templates.
#Should there be a specific required 'project' file of some sort? #Should there be a specific required 'project' file of some sort?
#(punkproject.toml is a candidate)
#we don't want to solely rely on such a file being present
# - we may also have punkproject.toml in project_layout template folders for example
#test for file/folder items indicating fossil or git workdir base #test for file/folder items indicating fossil or git workdir base
if {(![punk::repo::is_fossil_root $path]) && (![punk::repo::is_git_root $path])} { #the 'dev' mechanism for creating projects automatically creates a fossil project
#(which can be ignored if the user wants to manage it with git - but should probably remain in place? review)
#however - we currently require that for it to be a 'project' there must be some version control.
#REVIEW.
#
if {![punk::repo::is_repo_root $path]} {
return 0 return 0
} }
#exclude some known places we wouldn't want to put a project #exclude some known places we wouldn't want to put a project
if {![is_candidate_root $path]} { if {![is_candidate_root $path]} {
return 0 return 0
} }
@ -456,7 +523,7 @@ namespace eval punk::repo {
if {$abspath in [dict keys $defaults]} { if {$abspath in [dict keys $defaults]} {
set args [list $abspath {*}$args] set args [list $abspath {*}$args]
set abspath "" set abspath ""
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- ---
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
@ -793,7 +860,7 @@ namespace eval punk::repo {
} }
} }
if {$repotype eq "git"} { if {$repotype eq "git"} {
dict set fieldnames extra "extra (files/folders)" dict set fieldnames extra "extra (files/folders)"
} }
set col1_fields [list] set col1_fields [list]
set col2_values [list] set col2_values [list]
@ -846,6 +913,7 @@ namespace eval punk::repo {
#determine nature of possibly-nested repositories (of various types) at and above this path #determine nature of possibly-nested repositories (of various types) at and above this path
#Treat an untracked 'candidate' folder as a sort of repository #Treat an untracked 'candidate' folder as a sort of repository
proc find_repos {path} { proc find_repos {path} {
puts "find_repos '$path'"
set start_dir $path set start_dir $path
#root is a 'project' if it it meets the candidate requrements and is under repo control #root is a 'project' if it it meets the candidate requrements and is under repo control
@ -860,6 +928,10 @@ namespace eval punk::repo {
while {[string length [set fosroot [punk::repo::find_fossil $fos_search_from]]]} { while {[string length [set fosroot [punk::repo::find_fossil $fos_search_from]]]} {
lappend fossils_bottom_to_top $fosroot lappend fossils_bottom_to_top $fosroot
set fos_search_from [file dirname $fosroot] set fos_search_from [file dirname $fosroot]
if {$fos_search_from eq $fosroot} {
#root of filesystem is repo - unusual case - but without this we would never escape the while loop
break
}
} }
dict set root_dict fossil $fossils_bottom_to_top dict set root_dict fossil $fossils_bottom_to_top
@ -868,6 +940,9 @@ namespace eval punk::repo {
while {[string length [set gitroot [punk::repo::find_git $git_search_from]]]} { while {[string length [set gitroot [punk::repo::find_git $git_search_from]]]} {
lappend gits_bottom_to_top $gitroot lappend gits_bottom_to_top $gitroot
set git_search_from [file dirname $gitroot] set git_search_from [file dirname $gitroot]
if {$git_search_from eq $gitroot} {
break
}
} }
dict set root_dict git $gits_bottom_to_top dict set root_dict git $gits_bottom_to_top
@ -876,6 +951,9 @@ namespace eval punk::repo {
while {[string length [set candroot [punk::repo::find_candidate $cand_search_from]]]} { while {[string length [set candroot [punk::repo::find_candidate $cand_search_from]]]} {
lappend candidates_bottom_to_top $candroot lappend candidates_bottom_to_top $candroot
set cand_search_from [file dirname $candroot] set cand_search_from [file dirname $candroot]
if {$cand_search_from eq $candroot} {
break
}
} }
dict set root_dict candidate $candidates_bottom_to_top dict set root_dict candidate $candidates_bottom_to_top
@ -936,14 +1014,14 @@ namespace eval punk::repo {
dict set root_dict closest [lindex $longest_first 0 1] ;#the *path* of the closest to start_dir dict set root_dict closest [lindex $longest_first 0 1] ;#the *path* of the closest to start_dir
dict set root_dict closest_types [lindex $longest_first 0 0] dict set root_dict closest_types [lindex $longest_first 0 0]
} }
set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_fossil_len [llength [file split $closest_fossil]] set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_git [lindex [dict get $root_dict git] 0] set closest_fossil_len [llength [file split $closest_fossil]]
set closest_git_len [llength [file split $closest_git]] set closest_git [lindex [dict get $root_dict git] 0]
set closest_candidate [lindex [dict get $root_dict candidate] 0] set closest_git_len [llength [file split $closest_git]]
set closest_candidate_len [llength [file split $closest_candidate]] set closest_candidate [lindex [dict get $root_dict candidate] 0]
set closest_candidate_len [llength [file split $closest_candidate]]
if {$closest_candidate_len > $closest_fossil_len && $closest_candidate_len > $closest_git_len} { if {$closest_candidate_len > $closest_fossil_len && $closest_candidate_len > $closest_git_len} {
#only warn if this candidate is *within* a found repo root #only warn if this candidate is *within* a found repo root
@ -1079,7 +1157,7 @@ namespace eval punk::repo {
} }
if {$opt_ansi} { if {$opt_ansi} {
if {$opt_ansi_prompt eq "\uFFFF"} { if {$opt_ansi_prompt eq "\uFFFF"} {
set ansiprompt [a+ green bold] set ansiprompt [a+ green bold]
} else { } else {
set ansiprompt [$opt_ansi_prompt] set ansiprompt [$opt_ansi_prompt]
} }
@ -1112,15 +1190,15 @@ namespace eval punk::repo {
#Whilst it might detect a central repo folder in a non-standard location - it might also be annoying. #Whilst it might detect a central repo folder in a non-standard location - it might also be annoying.
#Todo - a separate environment variable for users to declare one or more locations where they would like to store project .fossil repositories? #Todo - a separate environment variable for users to declare one or more locations where they would like to store project .fossil repositories?
set candidate_repo_folder_locations [list] set candidate_repo_folder_locations [list]
#- choose a sensible default based on where fossil put the global config dir - or on the existence of a .fossils folder in a 'standard' location #- choose a sensible default based on where fossil put the global config dir - or on the existence of a .fossils folder in a 'standard' location
#verify with user before creating a .fossils folder #verify with user before creating a .fossils folder
#always check env(FOSSIL_HOME) first - but this is designed to locate the global .fossil (or _fossil) file - .fossils repository folder doesn't have to be at the same location #always check env(FOSSIL_HOME) first - but this is designed to locate the global .fossil (or _fossil) file - .fossils repository folder doesn't have to be at the same location
set usable_repo_folder_locations [list] set usable_repo_folder_locations [list]
#If we find one, but it's not writable - add it to another list #If we find one, but it's not writable - add it to another list
set readonly_repo_folder_locations [list] set readonly_repo_folder_locations [list]
#Examine a few possible locations for .fossils folder set #Examine a few possible locations for .fossils folder set
#if containing folder is writable add to candidate list #if containing folder is writable add to candidate list
set testpaths [list] set testpaths [list]
@ -1129,8 +1207,8 @@ namespace eval punk::repo {
if {![catch {package require Tcl 8.7-}]} { if {![catch {package require Tcl 8.7-}]} {
set fossilhome [file normalize [file tildeexpand $fossilhome_raw]] set fossilhome [file normalize [file tildeexpand $fossilhome_raw]]
} else { } else {
#8.6 #8.6
set fossilhome [file normalize $fossilhome_raw] set fossilhome [file normalize $fossilhome_raw]
} }
lappend testpaths [file join $fossilhome .fossils] lappend testpaths [file join $fossilhome .fossils]
@ -1175,13 +1253,13 @@ namespace eval punk::repo {
} }
} }
} }
set startdir_fossils [glob -nocomplain -dir $startdir -type f *.fossil] set startdir_fossils [glob -nocomplain -dir $startdir -type f *.fossil]
if {[llength $startdir_fossils]} { if {[llength $startdir_fossils]} {
#user is already keeping .fossil files directly in curent dir - give them the option to easily keep doing this #user is already keeping .fossil files directly in curent dir - give them the option to easily keep doing this
#(we don't add it if no .fossil files there already - as it is probably a niche requirement - or a sign the user hasn't thought about a better/central location) #(we don't add it if no .fossil files there already - as it is probably a niche requirement - or a sign the user hasn't thought about a better/central location)
if {$startdir ni $usable_repo_folder_locations} { if {$startdir ni $usable_repo_folder_locations} {
lappend usable_repo_folder_locations $startdir lappend usable_repo_folder_locations $startdir
} }
} }
set choice_folders [list] set choice_folders [list]
@ -1207,7 +1285,7 @@ namespace eval punk::repo {
#no existing writable .fossil folders (and no existing .fossil files in startdir) #no existing writable .fossil folders (and no existing .fossil files in startdir)
#offer the (writable) candidate_repo_folder_locations #offer the (writable) candidate_repo_folder_locations
foreach fld $candidate_repo_folder_locations { foreach fld $candidate_repo_folder_locations {
lappend choice_folders [list index $i folder $fld folderexists 0 existingfossils "" conflict ""] lappend choice_folders [list index $i folder $fld folderexists 0 existingfossils "" conflict ""]
incr i incr i
} }
} }
@ -1230,7 +1308,7 @@ namespace eval punk::repo {
} }
set folderexists [dict get $option folderexists] set folderexists [dict get $option folderexists]
if {$folderexists} { if {$folderexists} {
set folderstatus "(existing folder)" set folderstatus "(existing folder)"
} else { } else {
set folderstatus "(CREATE folder for .fossil repository files)" set folderstatus "(CREATE folder for .fossil repository files)"
} }
@ -1238,7 +1316,7 @@ namespace eval punk::repo {
} }
#append the readonly_repo_folder_locations so that user is aware of them as it may affect their choice #append the readonly_repo_folder_locations so that user is aware of them as it may affect their choice
if {[llength $readonly_repo_folder_locations]} { if {[llength $readonly_repo_folder_locations]} {
append menu_message "--------------------------------------------------" \n append menu_message "--------------------------------------------------" \n
foreach readonly $readonly_repo_folder_locations { foreach readonly $readonly_repo_folder_locations {
@ -1256,11 +1334,11 @@ namespace eval punk::repo {
} else { } else {
if {[llength $choice_folders] || $opt_askpath} { if {[llength $choice_folders] || $opt_askpath} {
puts stdout $menu_message puts stdout $menu_message
set max [llength $choice_folders] set max [llength $choice_folders]
if {$max == 1} { if {$max == 1} {
set rangemsg "the number 1" set rangemsg "the number 1"
} else { } else {
set rangemsg "a number from 1 to $max" set rangemsg "a number from 1 to $max"
} }
set menuprompt "${ansiprompt}Enter $rangemsg to select location. (or N to abort)${ansireset}" set menuprompt "${ansiprompt}Enter $rangemsg to select location. (or N to abort)${ansireset}"
if {$opt_askpath} { if {$opt_askpath} {
@ -1279,7 +1357,7 @@ namespace eval punk::repo {
set answer [askuser "${ansiprompt}Do you want to create this folder? Type just the word mkdir to create it, or N for no${ansireset}"] set answer [askuser "${ansiprompt}Do you want to create this folder? Type just the word mkdir to create it, or N for no${ansireset}"]
if {[string equal mkdir [string tolower $answer]]} { if {[string equal mkdir [string tolower $answer]]} {
if {[catch {file mkdir $repository_folder} errM]} { if {[catch {file mkdir $repository_folder} errM]} {
puts stderr "Failed to create folder $repository_folder. Error $errM" puts stderr "Failed to create folder $repository_folder. Error $errM"
} }
} }
} else { } else {
@ -1317,7 +1395,7 @@ namespace eval punk::repo {
if {$index >= 0 && $index <= $max-1} { if {$index >= 0 && $index <= $max-1} {
set repo_folder_choice [lindex $choice_folders $index] set repo_folder_choice [lindex $choice_folders $index]
set repository_folder [dict get $repo_folder_choice folder] set repository_folder [dict get $repo_folder_choice folder]
puts stdout "Selected fossil location $repository_folder" puts stdout "Selected fossil location $repository_folder"
} else { } else {
puts stderr " No menu number matched - aborting." puts stderr " No menu number matched - aborting."
return return
@ -1367,7 +1445,7 @@ namespace eval punk::repo {
proc fossil_revision {{path {}}} { proc fossil_revision {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
# ::kettle::path::revision.fossil # ::kettle::path::revision.fossil
set fossilcmd [Cached_auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
@ -1381,7 +1459,7 @@ namespace eval punk::repo {
proc fossil_remote {{path {}}} { proc fossil_remote {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
# ::kettle::path::revision.fossil # ::kettle::path::revision.fossil
set fossilcmd [Cached_auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
@ -1395,11 +1473,11 @@ namespace eval punk::repo {
proc fossil_get_configdb {{path {}}} { proc fossil_get_configdb {{path {}}} {
#fossil info will *usually* give us the necessary config-db info whether in a project folder or not but.. #fossil info will *usually* give us the necessary config-db info whether in a project folder or not but..
#a) It's expensive to shell-out and call it #a) It's expensive to shell-out and call it
#b) it won't give us a result if we are in a checkout folder which has had its repository moved #b) it won't give us a result if we are in a checkout folder which has had its repository moved
#this fairly extensive mechanism is designed to find it even if the environment has some weird goings-on regarding the filesystem/environment variables #this fairly extensive mechanism is designed to find it even if the environment has some weird goings-on regarding the filesystem/environment variables
#This is unlikely to be necessary in most scenarios, where the location is related to the user's home directory #This is unlikely to be necessary in most scenarios, where the location is related to the user's home directory
#attempt 1 - environment vars and well-known locations #attempt 1 - environment vars and well-known locations
#This is first because it's faster - but hopefully it's aligned with how fossil does it #This is first because it's faster - but hopefully it's aligned with how fossil does it
if {"windows" eq $::tcl_platform(platform)} { if {"windows" eq $::tcl_platform(platform)} {
@ -1416,7 +1494,7 @@ namespace eval punk::repo {
if {[file exists $testfile]} { if {[file exists $testfile]} {
return $testfile return $testfile
} }
} }
} else { } else {
foreach varname [list FOSSIL_HOME HOME ] { foreach varname [list FOSSIL_HOME HOME ] {
if {[info exists ::env($varname)]} { if {[info exists ::env($varname)]} {
@ -1435,13 +1513,13 @@ namespace eval punk::repo {
if {[file exists $testfile]} { if {[file exists $testfile]} {
return $testfile return $testfile
} }
} }
if {[info exists ::env(HOME)]} { if {[info exists ::env(HOME)]} {
set testfile [file join $::env(HOME) .config fossil.db] set testfile [file join $::env(HOME) .config fossil.db]
if {[file exists $testfile]} { if {[file exists $testfile]} {
return $testfile return $testfile
} }
} }
} }
@ -1484,13 +1562,13 @@ namespace eval punk::repo {
cd $original_cwd cd $original_cwd
} }
#attempt 3 - getting desperate.. find other repos, determine their checkouts and run fossil in them to get a result #attempt 3 - getting desperate.. find other repos, determine their checkouts and run fossil in them to get a result
if {$fossil_ok} { if {$fossil_ok} {
#It should be extremely rare to need to resort to sqlite on the databases to find other potential repo paths #It should be extremely rare to need to resort to sqlite on the databases to find other potential repo paths
#Conceivably only on some weird VFS or where some other filesystem strangeness is going on with our original path - or if the root volume itself is a broken fossil checkout #Conceivably only on some weird VFS or where some other filesystem strangeness is going on with our original path - or if the root volume itself is a broken fossil checkout
#Examining the other repos gives us a chance at discovering some other filesystem/paths where things may not be broken #Examining the other repos gives us a chance at discovering some other filesystem/paths where things may not be broken
if {![catch {package require sqlite3} errPackage]} { if {![catch {package require sqlite3} errPackage]} {
#use fossil all ls and sqlite #use fossil all ls and sqlite
if {[catch {exec {*}$fossilcmd all ls} repolines]} { if {[catch {exec {*}$fossilcmd all ls} repolines]} {
error "fossil_get_configdb cannot find repositories" error "fossil_get_configdb cannot find repositories"
} else { } else {
@ -1535,7 +1613,7 @@ namespace eval punk::repo {
error "fossil_get_configdb exhausted search options" error "fossil_get_configdb exhausted search options"
} }
#------------------------------------ #------------------------------------
#temporarily cd to workpath to run script - return to correct path even on failure #temporarily cd to workpath to run script - return to correct path even on failure
proc do_in_path {path script} { proc do_in_path {path script} {
#from ::kettle::path::in #from ::kettle::path::in
@ -1611,8 +1689,8 @@ namespace eval punk::repo {
set platform $::tcl_platform(platform) set platform $::tcl_platform(platform)
} }
#No - don't do this sort of path translation here - leave as option for specific utils only such as ./ #No - don't do this sort of path translation here - leave as option for specific utils only such as ./
#Windows volume-relative syntax with specific volume specified is somewhat broken in Tcl - but leading slash volume-relative does work #Windows volume-relative syntax with specific volume specified is somewhat broken in Tcl - but leading slash volume-relative does work
#We shouldn't break it totally just because accessing WSL/mingw paths is slightly more useful #We shouldn't break it totally just because accessing WSL/mingw paths is slightly more useful
#if {$platform eq "windows"} { #if {$platform eq "windows"} {
#return [file dirname [file normalize [punk::unixywindows::towinpath $path]/__]] #return [file dirname [file normalize [punk::unixywindows::towinpath $path]/__]]
@ -1624,7 +1702,7 @@ namespace eval punk::repo {
#This taken from kettle::path::strip #This taken from kettle::path::strip
#It doesn't compare the prefix contents presumably for speed when used in kettle::path::scan #It doesn't compare the prefix contents presumably for speed when used in kettle::path::scan
#renamed to better indicate its behaviour #renamed to better indicate its behaviour
proc path_strip_prefixdepth {path prefix} { proc path_strip_prefixdepth {path prefix} {
if {$prefix eq ""} { if {$prefix eq ""} {
return [norm $path] return [norm $path]
@ -1713,9 +1791,9 @@ namespace eval ::punk::args::register {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::repo [namespace eval punk::repo { package provide punk::repo [namespace eval punk::repo {
variable version variable version
set version 0.1.1 set version 0.1.1
}] }]
return return

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

@ -1,239 +1,239 @@
#utilities for punk apps to call #utilities for punk apps to call
package provide punkapp [namespace eval punkapp { package provide punkapp [namespace eval punkapp {
variable version variable version
set version 0.1 set version 0.1
}] }]
namespace eval punkapp { namespace eval punkapp {
variable result variable result
variable waiting "no" variable waiting "no"
proc hide_dot_window {} { proc hide_dot_window {} {
#alternative to wm withdraw . #alternative to wm withdraw .
#see https://wiki.tcl-lang.org/page/wm+withdraw #see https://wiki.tcl-lang.org/page/wm+withdraw
wm geometry . 1x1+0+0 wm geometry . 1x1+0+0
wm overrideredirect . 1 wm overrideredirect . 1
wm transient . wm transient .
} }
proc is_toplevel {w} { proc is_toplevel {w} {
if {![llength [info commands winfo]]} { if {![llength [info commands winfo]]} {
return 0 return 0
} }
expr {[winfo toplevel $w] eq $w && ![catch {$w cget -menu}]} expr {[winfo toplevel $w] eq $w && ![catch {$w cget -menu}]}
} }
proc get_toplevels {{w .}} { proc get_toplevels {{w .}} {
if {![llength [info commands winfo]]} { if {![llength [info commands winfo]]} {
return [list] return [list]
} }
set list {} set list {}
if {[is_toplevel $w]} { if {[is_toplevel $w]} {
lappend list $w lappend list $w
} }
foreach w [winfo children $w] { foreach w [winfo children $w] {
lappend list {*}[get_toplevels $w] lappend list {*}[get_toplevels $w]
} }
return $list return $list
} }
proc make_toplevel_next {prefix} { proc make_toplevel_next {prefix} {
set top [get_toplevel_next $prefix] set top [get_toplevel_next $prefix]
return [toplevel $top] return [toplevel $top]
} }
#possible race condition if multiple calls made without actually creating the toplevel, or gap if highest existing closed in the meantime #possible race condition if multiple calls made without actually creating the toplevel, or gap if highest existing closed in the meantime
#todo - reserve_toplevel_next ? keep list of toplevels considered 'allocated' even if never created or already destroyed? what usecase? #todo - reserve_toplevel_next ? keep list of toplevels considered 'allocated' even if never created or already destroyed? what usecase?
#can call wm withdraw to to reserve newly created toplevel. To stop re-use of existing names after destruction would require a list or at least a record of highest created for each prefix #can call wm withdraw to to reserve newly created toplevel. To stop re-use of existing names after destruction would require a list or at least a record of highest created for each prefix
proc get_toplevel_next {prefix} { proc get_toplevel_next {prefix} {
set base [string trim $prefix .] ;# .myapp -> myapp .myapp.somewindow -> myapp.somewindow . -> "" set base [string trim $prefix .] ;# .myapp -> myapp .myapp.somewindow -> myapp.somewindow . -> ""
} }
proc exit {{toplevel ""}} { proc exit {{toplevel ""}} {
variable waiting variable waiting
variable result variable result
variable default_result variable default_result
set toplevels [get_toplevels] set toplevels [get_toplevels]
if {[string length $toplevel]} { if {[string length $toplevel]} {
set wposn [lsearch $toplevels $toplevel] set wposn [lsearch $toplevels $toplevel]
if {$wposn > 0} { if {$wposn > 0} {
destroy $toplevel destroy $toplevel
} }
} else { } else {
#review #review
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} { if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
puts stderr "punkapp::exit called without toplevel - showing console" puts stderr "punkapp::exit called without toplevel - showing console"
show_console show_console
return 0 return 0
} else { } else {
puts stderr "punkapp::exit called without toplevel - exiting" puts stderr "punkapp::exit called without toplevel - exiting"
if {$waiting ne "no"} { if {$waiting ne "no"} {
if {[info exists result(shell)]} { if {[info exists result(shell)]} {
set temp [set result(shell)] set temp [set result(shell)]
unset result(shell) unset result(shell)
set waiting $temp set waiting $temp
} else { } else {
set waiting "" set waiting ""
} }
} else { } else {
::exit ::exit
} }
} }
} }
set controllable [get_user_controllable_toplevels] set controllable [get_user_controllable_toplevels]
if {![llength $controllable]} { if {![llength $controllable]} {
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} { if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
show_console show_console
} else { } else {
if {$waiting ne "no"} { if {$waiting ne "no"} {
if {[info exists result(shell)]} { if {[info exists result(shell)]} {
set temp [set result(shell)] set temp [set result(shell)]
unset result(shell) unset result(shell)
set waiting $temp set waiting $temp
} elseif {[info exists result($toplevel)]} { } elseif {[info exists result($toplevel)]} {
set temp [set result($toplevel)] set temp [set result($toplevel)]
unset result($toplevel) unset result($toplevel)
set waiting $temp set waiting $temp
} elseif {[info exists default_result]} { } elseif {[info exists default_result]} {
set temp $default_result set temp $default_result
unset default_result unset default_result
set waiting $temp set waiting $temp
} else { } else {
set waiting "" set waiting ""
} }
} else { } else {
::exit ::exit
} }
} }
} }
} }
proc close_window {toplevel} { proc close_window {toplevel} {
wm withdraw $toplevel wm withdraw $toplevel
if {![llength [get_user_controllable_toplevels]]} { if {![llength [get_user_controllable_toplevels]]} {
punkapp::exit $toplevel punkapp::exit $toplevel
} }
destroy $toplevel destroy $toplevel
} }
proc wait {args} { proc wait {args} {
variable waiting variable waiting
variable default_result variable default_result
if {[dict exists $args -defaultresult]} { if {[dict exists $args -defaultresult]} {
set default_result [dict get $args -defaultresult] set default_result [dict get $args -defaultresult]
} }
foreach t [punkapp::get_toplevels] { foreach t [punkapp::get_toplevels] {
if {[wm protocol $t WM_DELETE_WINDOW] eq ""} { if {[wm protocol $t WM_DELETE_WINDOW] eq ""} {
wm protocol $t WM_DELETE_WINDOW [list punkapp::close_window $t] wm protocol $t WM_DELETE_WINDOW [list punkapp::close_window $t]
} }
} }
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} { if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
puts stderr "repl eventloop seems to be running - punkapp::wait not required" puts stderr "repl eventloop seems to be running - punkapp::wait not required"
} else { } else {
if {$waiting eq "no"} { if {$waiting eq "no"} {
set waiting "waiting" set waiting "waiting"
vwait ::punkapp::waiting vwait ::punkapp::waiting
return $::punkapp::waiting return $::punkapp::waiting
} }
} }
} }
#A window can be 'visible' according to this - but underneath other windows etc #A window can be 'visible' according to this - but underneath other windows etc
#REVIEW - change name? #REVIEW - change name?
proc get_visible_toplevels {{w .}} { proc get_visible_toplevels {{w .}} {
if {![llength [info commands winfo]]} { if {![llength [info commands winfo]]} {
return [list] return [list]
} }
set list [get_toplevels $w] set list [get_toplevels $w]
set mapped [lmap v $list {expr {[winfo ismapped $v] ? $v : {}}}] set mapped [lmap v $list {expr {[winfo ismapped $v] ? $v : {}}}]
set mapped [concat {*}$mapped] ;#ignore {} set mapped [concat {*}$mapped] ;#ignore {}
set visible [list] set visible [list]
foreach m $mapped { foreach m $mapped {
if {[wm overrideredirect $m] == 0 } { if {[wm overrideredirect $m] == 0 } {
lappend visible $m lappend visible $m
} else { } else {
if {[winfo height $m] >1 && [winfo width $m] > 1} { if {[winfo height $m] >1 && [winfo width $m] > 1} {
#technically even a 1x1 is visible.. but in practice even a 10x10 is hardly likely to be noticeable when overrideredirect == 1 #technically even a 1x1 is visible.. but in practice even a 10x10 is hardly likely to be noticeable when overrideredirect == 1
#as a convention - 1x1 with no controls is used to make a window invisible so we'll treat anything larger as visible #as a convention - 1x1 with no controls is used to make a window invisible so we'll treat anything larger as visible
lappend visible $m lappend visible $m
} }
} }
} }
return $visible return $visible
} }
proc get_user_controllable_toplevels {{w .}} { proc get_user_controllable_toplevels {{w .}} {
set visible [get_visible_toplevels $w] set visible [get_visible_toplevels $w]
set controllable [list] set controllable [list]
foreach v $visible { foreach v $visible {
if {[wm overrideredirect $v] == 0} { if {[wm overrideredirect $v] == 0} {
lappend controllable $v lappend controllable $v
} }
} }
#only return visible windows with overrideredirect == 0 because there exists some user control. #only return visible windows with overrideredirect == 0 because there exists some user control.
#todo - review.. consider checking if position is outside screen areas? Technically controllable.. but not easily #todo - review.. consider checking if position is outside screen areas? Technically controllable.. but not easily
return $controllable return $controllable
} }
proc hide_console {args} { proc hide_console {args} {
set opts [dict create -force 0] set opts [dict create -force 0]
if {([llength $args] % 2) != 0} { if {([llength $args] % 2) != 0} {
error "hide_console expects pairs of arguments. e.g -force 1" error "hide_console expects pairs of arguments. e.g -force 1"
} }
#set known_opts [dict keys $defaults] #set known_opts [dict keys $defaults]
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-force { -force {
dict set opts $k $v dict set opts $k $v
} }
default { default {
error "Unrecognised options '$k' known options: [dict keys $opts]" error "Unrecognised options '$k' known options: [dict keys $opts]"
} }
} }
} }
set force [dict get $opts -force] set force [dict get $opts -force]
if {!$force} { if {!$force} {
if {![llength [get_user_controllable_toplevels]]} { if {![llength [get_user_controllable_toplevels]]} {
puts stderr "Cannot hide console while no user-controllable windows available" puts stderr "Cannot hide console while no user-controllable windows available"
return 0 return 0
} }
} }
if {$::tcl_platform(platform) eq "windows"} { if {$::tcl_platform(platform) eq "windows"} {
#hide won't work for certain consoles cush as conemu,wezterm - and doesn't really make sense for tabbed windows anyway. #hide won't work for certain consoles cush as conemu,wezterm - and doesn't really make sense for tabbed windows anyway.
#It would be nice if we could tell the console window to hide just the relevant tab - or the whole window if only one tab present - but this is unlikely to be possible in any standard way. #It would be nice if we could tell the console window to hide just the relevant tab - or the whole window if only one tab present - but this is unlikely to be possible in any standard way.
#an ordinary cmd.exe or pwsh.exe or powershell.exe window can be hidden ok though. #an ordinary cmd.exe or pwsh.exe or powershell.exe window can be hidden ok though.
#(but with wezterm - process is cmd.exe - but it has style popup and can't be hidden with a twapi::hide_window call) #(but with wezterm - process is cmd.exe - but it has style popup and can't be hidden with a twapi::hide_window call)
package require twapi package require twapi
set h [twapi::get_console_window] set h [twapi::get_console_window]
set pid [twapi::get_window_process $h] set pid [twapi::get_window_process $h]
set pinfo [twapi::get_process_info $pid -name] set pinfo [twapi::get_process_info $pid -name]
set pname [dict get $pinfo -name] set pname [dict get $pinfo -name]
set wstyle [twapi::get_window_style $h] set wstyle [twapi::get_window_style $h]
#tclkitsh/tclsh? #tclkitsh/tclsh?
if {($pname in [list cmd.exe pwsh.exe powershell.exe] || [string match punk*.exe $pname]) && "popup" ni $wstyle} { if {($pname in [list cmd.exe pwsh.exe powershell.exe] || [string match punk*.exe $pname]) && "popup" ni $wstyle} {
twapi::hide_window $h twapi::hide_window $h
return 1 return 1
} else { } else {
puts stderr "punkapp::hide_console unable to hide this type of console window" puts stderr "punkapp::hide_console unable to hide this type of console window"
return 0 return 0
} }
} else { } else {
#todo #todo
puts stderr "punkapp::hide_console unimplemented on this platform (todo)" puts stderr "punkapp::hide_console unimplemented on this platform (todo)"
return 0 return 0
} }
} }
proc show_console {} { proc show_console {} {
if {$::tcl_platform(platform) eq "windows"} { if {$::tcl_platform(platform) eq "windows"} {
package require twapi package require twapi
if {![catch {set h [twapi::get_console_window]} errM]} { if {![catch {set h [twapi::get_console_window]} errM]} {
twapi::show_window $h -activate -normal twapi::show_window $h -activate -normal
} else { } else {
#no console - assume launched from something like wish? #no console - assume launched from something like wish?
catch {console show} catch {console show}
} }
} else { } else {
#todo #todo
puts stderr "punkapp::show_console unimplemented on this platform" puts stderr "punkapp::show_console unimplemented on this platform"
} }
} }
} }

114
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm

@ -243,12 +243,14 @@ namespace eval punkcheck {
} }
method get_targets_exist {} { method get_targets_exist {} {
set punkcheck_folder [file dirname [$o_installer get_checkfile]] set punkcheck_folder [file dirname [$o_installer get_checkfile]]
set existing [list] set existing [glob -nocomplain -dir $punkcheck_folder -tails {*}$o_targets]
foreach t $o_targets {
if {[file exists [file join $punkcheck_folder $t]]} { #set existing [list]
lappend existing $t #foreach t $o_targets {
} # if {[file exists [file join $punkcheck_folder $t]]} {
} # lappend existing $t
# }
#}
return $existing return $existing
} }
method end {} { method end {} {
@ -880,19 +882,46 @@ namespace eval punkcheck {
#allow nonexistant as a source #allow nonexistant as a source
set fpath [file join $punkcheck_folder $source_relpath] set fpath [file join $punkcheck_folder $source_relpath]
if {![file exists $fpath]} { #windows: file exist + file type = 2ms vs 500ms for 2x glob
set floc [file dirname $fpath]
set fname [file tail $fpath]
set file_set [glob -nocomplain -dir $floc -type f -tails $fname]
set dir_set [glob -nocomplain -dir $floc -type d -tails $fname]
set link_set [glob -nocomplain -dir $floc -type l -tails $fname]
if {[llength $file_set] == 0 && [llength $dir_set] == 0 && [llength $link_set] == 0} {
#could also theoretically exist as less common types, b,c,p,s (block,char,pipe,socket)
#- we don't expect them here - REVIEW - ever possible?
#- installing/examining such things an unlikely usecase and would require special handling anyway.
set ftype "missing" set ftype "missing"
set fsize "" set fsize ""
} else { } else {
set ftype [file type $fpath] if {[llength $dir_set]} {
if {$ftype eq "directory"} { set ftype "directory"
set fsize "NA" set fsize "NA"
} elseif {[llength $link_set]} {
set ftype "link"
set fsize 0
} else { } else {
set ftype "file"
#todo - optionally use mtime instead of cksum (for files only)? #todo - optionally use mtime instead of cksum (for files only)?
#mtime is not reliable across platforms and filesystems though.. see article linked at top. #mtime is not reliable across platforms and filesystems though.. see article linked at top.
set fsize [file size $fpath] set fsize [file size $fpath]
} }
} }
#if {![file exists $fpath]} {
# set ftype "missing"
# set fsize ""
#} else {
# set ftype [file type $fpath]
# if {$ftype eq "directory"} {
# set fsize "NA"
# } else {
# #todo - optionally use mtime instead of cksum (for files only)?
# #mtime is not reliable across platforms and filesystems though.. see article linked at top.
# set fsize [file size $fpath]
# }
#}
#get_relativecksum_from_base and fill_relativecksums_from_base_and_relativepathdict will set cksum to <PATHNOTFOUND> if fpath doesn't exist #get_relativecksum_from_base and fill_relativecksums_from_base_and_relativepathdict will set cksum to <PATHNOTFOUND> if fpath doesn't exist
if {$use_cache} { if {$use_cache} {
set source_cksum_info [punk::mix::base::lib::fill_relativecksums_from_base_and_relativepathdict $punkcheck_folder [dict create $source_relpath $use_cache_record]] set source_cksum_info [punk::mix::base::lib::fill_relativecksums_from_base_and_relativepathdict $punkcheck_folder [dict create $source_relpath $use_cache_record]]
@ -1648,6 +1677,10 @@ namespace eval punkcheck {
set is_skip 0 set is_skip 0
if {$overwrite_what eq "all-targets"} { if {$overwrite_what eq "all-targets"} {
file mkdir $current_target_dir file mkdir $current_target_dir
#--------------------------------------------
#sometimes we get the error: 'error copying "file1" to "file2": invalid argument'
#--------------------------------------------
puts stderr "punkcheck: about to: file copy -force $current_source_dir/$m $current_target_dir"
file copy -force $current_source_dir/$m $current_target_dir file copy -force $current_source_dir/$m $current_target_dir
lappend files_copied $current_source_dir/$m lappend files_copied $current_source_dir/$m
} else { } else {
@ -1859,22 +1892,75 @@ namespace eval punkcheck {
return [list files_copied $files_copied files_skipped $files_skipped sources_unchanged $sources_unchanged antiglob_paths_matched $antiglob_paths_matched punkcheck_records $punkcheck_records punkcheck_folder $punkcheck_folder srcdir $srcdir tgtdir $tgtdir] return [list files_copied $files_copied files_skipped $files_skipped sources_unchanged $sources_unchanged antiglob_paths_matched $antiglob_paths_matched punkcheck_records $punkcheck_records punkcheck_folder $punkcheck_folder srcdir $srcdir tgtdir $tgtdir]
} }
proc summarize_install_resultdict {resultdict} {
lappend PUNKARGS [list {
@id -id ::punkcheck::summarize_install_resultdict
@cmd -name punkcheck::summarize_install_resultdict -help\
"Emits a string summarizing a punkcheck resultdict, showing
how many items were copied, and the source, target locations"
@opts
-title -type string -default ""
-forcecolour -type boolean -default 0 -help\
"When true, passes the forcecolour tag to punk::ansi functions.
This enables ANSI sgr colours even when colour
is off. (ignoring env(NO_COLOR))
To disable colour - ensure the NO_COLOR env var is set,
or use:
namespace eval ::punk::console {variable colour_disabled 1}"
@values -min 1 -max 1
resultdict -type dict
}]
proc summarize_install_resultdict {args} {
set argd [punk::args::parse $args withid ::punkcheck::summarize_install_resultdict]
lassign [dict values $argd] leaders opts values received
set title [dict get $opts -title]
set forcecolour [dict get $opts -forcecolour]
set resultdict [dict get $values resultdict]
set has_ansi [expr {![catch {package require punk::ansi}]}]
if {$has_ansi} {
if {$forcecolour} {
set fc "forcecolour"
} else {
set fc ""
}
set R [punk::ansi::a] ;#reset
set LINE_COLOUR [punk::ansi::a+ {*}$forcecolour bold cyan]
set LOW_COLOUR [punk::ansi::a+ {*}$forcecolour bold green]
set HIGH_COLOUR [punk::ansi::a+ {*}$forcecolour bold yellow]
} else {
set R ""
set LINE_COLOUR ""
set LOW_COLOUR ""
set HIGH_COLOUR ""
}
set msg "" set msg ""
if {[dict size $resultdict]} { if {[dict size $resultdict]} {
set copied [dict get $resultdict files_copied] set copied [dict get $resultdict files_copied]
append msg "--------------------------" \n if {[llength $copied] == 0} {
append msg "[dict keys $resultdict]" \n set HIGHLIGHT $LOW_COLOUR
} else {
set HIGHLIGHT $HIGH_COLOUR
}
set ruler $LINE_COLOUR[string repeat - 78]$R
if {$title ne ""} {
append msg $ruler \n
append msg $title \n
}
append msg $ruler \n
#append msg "[dict keys $resultdict]" \n
set tgtdir [dict get $resultdict tgtdir] set tgtdir [dict get $resultdict tgtdir]
set checkfolder [dict get $resultdict punkcheck_folder] set checkfolder [dict get $resultdict punkcheck_folder]
append msg "Copied [llength $copied] files from [dict get $resultdict srcdir] to [dict get $resultdict tgtdir]" \n append msg "${HIGHLIGHT}Copied [llength $copied] files from [dict get $resultdict srcdir] to [dict get $resultdict tgtdir]$R" \n
foreach f $copied { foreach f $copied {
append msg "COPIED [punkcheck::lib::path_relative $checkfolder $f]" \n append msg "COPIED [punkcheck::lib::path_relative $checkfolder $f]" \n
append msg " TO $tgtdir" \n append msg " TO $tgtdir" \n
} }
append msg "[llength [dict get $resultdict sources_unchanged]] unchanged source files" \n append msg "[llength [dict get $resultdict sources_unchanged]] unchanged source files" \n
append msg "[llength [dict get $resultdict files_skipped]] skipped files" \n append msg "[llength [dict get $resultdict files_skipped]] skipped files" \n
append msg "--------------------------" \n append msg $ruler \n
} }
return $msg return $msg
} }

BIN
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.1.tm

Binary file not shown.

BIN
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.3.tm

Binary file not shown.

160
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/tomlish-1.1.2.tm

@ -185,6 +185,8 @@ namespace eval tomlish {
error "tomlish _get_keyval_value invalid to have type TABLE on rhs of =" error "tomlish _get_keyval_value invalid to have type TABLE on rhs of ="
} }
ITABLE { ITABLE {
#This one should not be returned as a type <tag> value <something> structure!
#
set result [::tomlish::to_dict [list $found_sub]] set result [::tomlish::to_dict [list $found_sub]]
} }
ARRAY { ARRAY {
@ -249,6 +251,7 @@ namespace eval tomlish {
} }
#to_dict is a *basic* programmatic datastructure for accessing the data. #to_dict is a *basic* programmatic datastructure for accessing the data.
# produce a dictionary of keys and values from a tomlish tagged list. # produce a dictionary of keys and values from a tomlish tagged list.
# to_dict is primarily for reading toml data. # to_dict is primarily for reading toml data.
@ -271,8 +274,12 @@ namespace eval tomlish {
# so we can raise an error to satisfy the toml rule: 'You cannot define any key or table more than once. Doing so is invalid' # so we can raise an error to satisfy the toml rule: 'You cannot define any key or table more than once. Doing so is invalid'
#Note that [a] and then [a.b] is ok if there are no subkey conflicts - so we are only tracking complete tablenames here. #Note that [a] and then [a.b] is ok if there are no subkey conflicts - so we are only tracking complete tablenames here.
#we don't error out just because a previous tablename segment has already appeared. #we don't error out just because a previous tablename segment has already appeared.
variable tablenames_seen [list] ##variable tablenames_seen [list]
if {[uplevel 1 [list info exists tablenames_seen]]} {
upvar tablenames_seen tablenames_seen
} else {
set tablenames_seen [list]
}
log::info ">>> processing '$tomlish'<<<" log::info ">>> processing '$tomlish'<<<"
set items $tomlish set items $tomlish
@ -311,9 +318,9 @@ namespace eval tomlish {
} }
DOTTEDKEY { DOTTEDKEY {
log::debug "--> processing $tag: $item" log::debug "--> processing $tag: $item"
set dkey_info [_get_dottedkey_info $item] set dkey_info [_get_dottedkey_info $item]
set dotted_key_hierarchy [dict get $dkey_info keys] set dotted_key_hierarchy [dict get $dkey_info keys]
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw] set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw]
#a.b.c = 1 #a.b.c = 1
#table_key_hierarchy -> a b #table_key_hierarchy -> a b
@ -345,6 +352,9 @@ namespace eval tomlish {
set keyval_dict [_get_keyval_value $item] set keyval_dict [_get_keyval_value $item]
dict set datastructure {*}$pathkeys $leafkey $keyval_dict dict set datastructure {*}$pathkeys $leafkey $keyval_dict
#JMN test 2025
} }
TABLE { TABLE {
set tablename [lindex $item 1] set tablename [lindex $item 1]
@ -386,8 +396,40 @@ namespace eval tomlish {
lappend table_key_hierarchy_raw $rawseg lappend table_key_hierarchy_raw $rawseg
if {[dict exists $datastructure {*}$table_key_hierarchy]} { if {[dict exists $datastructure {*}$table_key_hierarchy]} {
#It's ok for this key to already exist *if* it was defined by a previous tablename, #It's ok for this key to already exist *if* it was defined by a previous tablename or equivalent
# but not if it was defined as a key/qkey/skey ? #and if this key is longer
#consider the following 2 which are legal:
#[table]
#x.y = 3
#[table.x.z]
#k= 22
#equivalent
#[table]
#[table.x]
#y = 3
#[table.x.z]
#k=22
#illegal
#[table]
#x.y = 3
#[table.x.y.z]
#k = 22
## - we should bfail on encoungerint table.x.y because only table and table.x are effectively tables
## - we should also fail if
#illegal
#[table]
#x.y = {p=3}
#[table.x.y.z]
#k = 22
## we should fail because y is an inline table which is closed to further entries
#TODO! fix - this code is wrong
set testkey [join $table_key_hierarchy_raw .] set testkey [join $table_key_hierarchy_raw .]
@ -422,7 +464,7 @@ namespace eval tomlish {
if {$found_testkey == 0} { if {$found_testkey == 0} {
#the raw table_key_hierarchy is better to display in the error message, although it's not the actual dict keyset #the raw table_key_hierarchy is better to display in the error message, although it's not the actual dict keyset
set msg "key [join $table_key_hierarchy_raw .] already exists in datastructure, but wasn't defined by a supertable." set msg "key [join $table_key_hierarchy_raw .] already exists in datastructure, but wasn't defined by a supertable."
append msg "tablenames_seen:" append msg \n "tablenames_seen:" \n
foreach ts $tablenames_seen { foreach ts $tablenames_seen {
append msg " " $ts \n append msg " " $ts \n
} }
@ -453,13 +495,18 @@ namespace eval tomlish {
#now add the contained elements #now add the contained elements
foreach element [lrange $item 2 end] { foreach element [lrange $item 2 end] {
set type [lindex $element 0] set type [lindex $element 0]
log::debug "--> $type processing contained element $element"
switch -exact -- $type { switch -exact -- $type {
DOTTEDKEY { DOTTEDKEY {
set dkey_info [_get_dottedkey_info $element] set dkey_info [_get_dottedkey_info $element]
set dotted_key_hierarchy [dict get $dkey_info keys] #e.g1 keys {x.y y} keys_raw {'x.y' y}
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw] #e.g2 keys {x.y y} keys_raw {{"x.y"} y}
set leaf_key [lindex $dotted_key_hierarchy end] set dotted_key_hierarchy [dict get $dkey_info keys]
set dkeys [lrange $dotted_key_hierarchy 0 end-1] set dkeys [lrange $dotted_key_hierarchy 0 end-1]
set leaf_key [lindex $dotted_key_hierarchy end]
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw]
set dkeys_raw [lrange $dotted_key_hierarchy_raw 0 end-1]
set leaf_key_raw [lindex $dotted_key_hierarchy_raw end]
#ensure empty keys are still represented in the datastructure #ensure empty keys are still represented in the datastructure
set test_keys $table_keys set test_keys $table_keys
@ -476,7 +523,22 @@ namespace eval tomlish {
error "Duplicate key '$table_keys $dkeys $leaf_key'. The key already exists at this level in the toml data. The toml data is not valid." error "Duplicate key '$table_keys $dkeys $leaf_key'. The key already exists at this level in the toml data. The toml data is not valid."
} }
set keyval_dict [_get_keyval_value $element] set keyval_dict [_get_keyval_value $element]
#keyval_dict is either a {type <tomltag> value <whatever>}
#or the result from parsing an arbitrary dict from an inline table - which could theoretically look the same at the topmost level
#punk::dict::is_tomlish_typeval can distinguish
puts stdout ">>> $keyval_dict"
dict set datastructure {*}$table_keys {*}$dkeys $leaf_key $keyval_dict dict set datastructure {*}$table_keys {*}$dkeys $leaf_key $keyval_dict
#JMN 2025
#tomlish::utils::normalize_key ??
lappend tablenames_seen [join [list {*}$table_key_hierarchy_raw {*}$dkeys_raw] .] ;#????
#if the keyval_dict is not a simple type x value y - then it's an inline table ?
#if so - we should add the path to the leaf_key as a seen table too - as it's not allowed to have more entries added.
if {![tomlish::dict::is_tomlish_typeval $keyval_dict]} {
#the value is either empty or or a dict structure with arbitrary (from-user-data) toplevel keys
# inner structure will contain {type <tag> value <etc>} if all leaves are not empty ITABLES
lappend tablenames_seen [join [list {*}$table_key_hierarchy_raw {*}$dkeys_raw $leaf_key_raw] .]
}
} }
KEY - QKEY - SQKEY { KEY - QKEY - SQKEY {
#obsolete ? #obsolete ?
@ -777,7 +839,7 @@ namespace eval tomlish {
set result [list] set result [list]
set lastparent [lindex $parents end] set lastparent [lindex $parents end]
if {$lastparent in [list "" do_inline]} { if {$lastparent in [list "" do_inline]} {
if {[tomlish::dict::is_tomltype $vinfo]} { if {[tomlish::dict::is_tomlish_typeval $vinfo]} {
set type [dict get $vinfo type] set type [dict get $vinfo type]
#treat ITABLE differently? #treat ITABLE differently?
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo]
@ -811,7 +873,7 @@ namespace eval tomlish {
} else { } else {
set VK_PART [list KEY $vk] set VK_PART [list KEY $vk]
} }
if {[tomlish::dict::is_tomltype $vv]} { if {[tomlish::dict::is_tomlish_typeval $vv]} {
#type x value y #type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv]
set record [list DOTTEDKEY [list $VK_PART {WS { }}] = {WS { }} $sublist] set record [list DOTTEDKEY [list $VK_PART {WS { }}] = {WS { }} $sublist]
@ -877,7 +939,7 @@ namespace eval tomlish {
} }
} else { } else {
#lastparent is not toplevel "" or "do_inline" #lastparent is not toplevel "" or "do_inline"
if {[tomlish::dict::is_tomltype $vinfo]} { if {[tomlish::dict::is_tomlish_typeval $vinfo]} {
#type x value y #type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo]
lappend result {*}$sublist lappend result {*}$sublist
@ -901,7 +963,7 @@ namespace eval tomlish {
} else { } else {
set VK_PART [list KEY $vk] set VK_PART [list KEY $vk]
} }
if {[tomlish::dict::is_tomltype $vv]} { if {[tomlish::dict::is_tomlish_typeval $vv]} {
#type x value y #type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv] set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv]
set record [list DOTTEDKEY [list $VK_PART] = $sublist] set record [list DOTTEDKEY [list $VK_PART] = $sublist]
@ -2404,7 +2466,8 @@ namespace eval tomlish::utils {
} ;#RS } ;#RS
#check if str is valid for use as a toml bare key #check if str is valid for use as a toml bare key
proc is_barekey {str} { #Early toml versions? only allowed letters + underscore + dash
proc is_barekey1 {str} {
if {[tcl::string::length $str] == 0} { if {[tcl::string::length $str] == 0} {
return 0 return 0
} else { } else {
@ -2418,6 +2481,52 @@ namespace eval tomlish::utils {
} }
} }
#from toml.abnf in github.com/toml-lang/toml
#unquoted-key = 1*unquoted-key-char
#unquoted-key-char = ALPHA / DIGIT / %x2D / %x5F ; a-z A-Z 0-9 - _
#unquoted-key-char =/ %xB2 / %xB3 / %xB9 / %xBC-BE ; superscript digits, fractions
#unquoted-key-char =/ %xC0-D6 / %xD8-F6 / %xF8-37D ; non-symbol chars in Latin block
#unquoted-key-char =/ %x37F-1FFF ; exclude GREEK QUESTION MARK, which is basically a semi-colon
#unquoted-key-char =/ %x200C-200D / %x203F-2040 ; from General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ
#unquoted-key-char =/ %x2070-218F / %x2460-24FF ; include super-/subscripts, letterlike/numberlike forms, enclosed alphanumerics
#unquoted-key-char =/ %x2C00-2FEF / %x3001-D7FF ; skip arrows, math, box drawing etc, skip 2FF0-3000 ideographic up/down markers and spaces
#unquoted-key-char =/ %x2070-21FF / %x2300-24FF ; skip math operators
#unquoted-key-char =/ %x25A0-268B / %x2690-2757 ; skip box drawing, block elements, and some yin-yang symbols
#unquoted-key-char =/ %x2762-2767 / %x2776-27E5 ; skip some Dingbat punctuation
#unquoted-key-char =/ %x2801-297F ; skip some math brackets and arrows, and braille blank
#unquoted-key-char =/ %x2B00-2FFF / %x3001-D7FF ; skip various math operators and symbols, and ideographic space
#unquoted-key-char =/ %xF900-FDCF / %xFDF0-FFFD ; skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF intended for process-internal use (unicode)
#unquoted-key-char =/ %x10000-EFFFF ; all chars outside BMP range, excluding Private Use planes (F0000-10FFFF)
variable re_barekey
set ranges [list]
lappend ranges {a-zA-Z0-9\_\-}
lappend ranges {\u00B2} {\u00B3} {\u00B9} {\u00BC-\u00BE} ;# superscript digits, fractions
lappend ranges {\u00C0-\u00D6} {\u00D8-\u00F6} {\u00F8-\u037D} ;# non-symbol chars in Latin block
lappend ranges {\u037f-\u1FFF} ;# exclude GREEK QUESTION MARK, which is basically a semi-colon
lappend ranges {\u200C-\u200D} {\u203F-\u2040} ;# from General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ
lappend ranges {\u2070-\u218f} {\u2460-\u24FF} ;# include super-subscripts, letterlike/numberlike forms, enclosed alphanumerics
lappend ranges {\u2C00-\u2FEF} {\u3001-\uD7FF} ;# skip arrows, math, box drawing etc, skip 2FF0-3000 ideographic up/down markers and spaces
lappend ranges {\u2070-\u21FF} {\u2300-\u24FF} ;# skip math operators
lappend ranges {\u25A0-\u268B} {\u2690-\u2757} ;# skip box drawing, block elements, and some yin-yang symbols
lappend ranges {\u2762-\u2767} {\u2776-\u27E5} ;# skip some Dingbat punctuation
lappend ranges {\u2801-\u297F} ;# skip some math brackets and arrows, and braille blank
lappend ranges {\u2B00-\u2FFF} {\u3001-\uD7FF} ;# skip various math operators and symbols, and ideographic space
lappend ranges {\uF900-\uFDCF} {\uFDF0-\uFFFD} ;# skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF intended for process-internal use (unicode)
lappend ranges {\U10000-\UEFFFF} ;# all chars outside BMP range, excluding Private Use planes (F0000-10FFFF)
set re_barekey {^[}
foreach r $ranges {
append re_barekey $r
}
append re_barekey {]+$}
proc is_barekey {str} {
if {[tcl::string::length $str] == 0} {
return 0
}
variable re_barekey
return [regexp $re_barekey $str]
}
#test only that the characters in str are valid for the toml specified type 'integer'. #test only that the characters in str are valid for the toml specified type 'integer'.
proc int_validchars1 {str} { proc int_validchars1 {str} {
set numchars [tcl::string::length $str] set numchars [tcl::string::length $str]
@ -3471,7 +3580,7 @@ namespace eval tomlish::parse {
return 1 return 1
} }
barekey { barekey {
error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z_-\] allowed. [tomlish::parse::report_line]" error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z0-9_-\] and a selection of letter-like chars allowed (see tomlish::utils::is_barekey). [tomlish::parse::report_line]"
} }
whitespace { whitespace {
# hash marks end of whitespace token # hash marks end of whitespace token
@ -5222,7 +5331,7 @@ namespace eval tomlish::parse {
if {[tomlish::utils::is_barekey $c]} { if {[tomlish::utils::is_barekey $c]} {
append tok $c append tok $c
} else { } else {
error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z0-9_-\] allowed. [tomlish::parse::report_line]" error "tomlish Unexpected character '$c' during bare key. Only \[a-zA-Z0-9_-\] and a selection of letter-like chars allowed. (see tomlish::utils::is_barekey) [tomlish::parse::report_line]"
} }
} }
starttablename - starttablearrayname { starttablename - starttablearrayname {
@ -5354,10 +5463,15 @@ namespace eval tomlish::dict {
namespace export {[a-z]*}; # Convention: export all lowercase namespace export {[a-z]*}; # Convention: export all lowercase
namespace path [namespace parent] namespace path [namespace parent]
proc is_tomltype {d} { proc is_tomlish_typeval {d} {
expr {[dict size $d] == 2 && [dict exists $d type] && [dict exists $d value]} #designed to detect {type <tag> value <whatever>} e.g {type INT value 3}, {type STRING value "blah etc"}
#as a sanity check we need to avoid mistaking user data that happens to match same form
#consider x.y={type="spud",value="blah"}
#The value of type will itself have already been converted to {type STRING value spud} ie never a single element.
#check the length of the type as a quick way to see it's a tag - not something else masqerading.
expr {[dict size $d] == 2 && [dict exists $d type] && [dict exists $d value] && [llength [dict get $d type]] == 1}
} }
proc is_tomltype2 {d} { proc is_tomlish_typeval2 {d} {
upvar ::tomlish::tags tags upvar ::tomlish::tags tags
expr {[lindex $d 0] eq "type" && [lindex $d 1] in $tags} expr {[lindex $d 0] eq "type" && [lindex $d 1] in $tags}
} }
@ -5366,7 +5480,7 @@ namespace eval tomlish::dict {
set dictposn [expr {[dict size $d] -1}] set dictposn [expr {[dict size $d] -1}]
foreach k [lreverse [dict keys $d]] { foreach k [lreverse [dict keys $d]] {
set dval [dict get $d $k] set dval [dict get $d $k]
if {[is_tomltype $dval]} { if {[is_tomlish_typeval $dval]} {
set last_simple $dictposn set last_simple $dictposn
break break
} }

6002
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/tomlish-1.1.3.tm

File diff suppressed because it is too large Load Diff

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

@ -2,12 +2,15 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1}
}
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
puts " Punk Boot" puts " Punk Boot"
puts $hashline\n puts $hashline\n
package prefer latest package prefer latest
lassign [split [info tclversion] .] tclmajorv tclminorv lassign [split [info tclversion] .] tclmajorv tclminorv
global A ;#UI Ansi code array global A ;#UI Ansi code array
@ -104,7 +107,7 @@ namespace eval ::punkboot::lib {
} }
} }
return [join $newparts .] return [join $newparts .]
} }
proc tm_version_required_canonical {versionspec} { proc tm_version_required_canonical {versionspec} {
#also trim leading zero from any dottedpart? #also trim leading zero from any dottedpart?
#Tcl *allows* leading zeros in any of the dotted parts - but they are not significant. #Tcl *allows* leading zeros in any of the dotted parts - but they are not significant.
@ -112,10 +115,10 @@ namespace eval ::punkboot::lib {
#also 1b3 == 1b0003 #also 1b3 == 1b0003
if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version
set errmsg "punkboot::lib::tm_version_required_canonical - invalid version specification" set errmsg "punkboot::lib::tm_version_required_canonical - invalid version specification"
if {[string first - $versionspec] < 0} { if {[string first - $versionspec] < 0} {
#no dash #no dash
#looks like a minbounded version (ie a single version with no dash) convert to min-max form #looks like a minbounded version (ie a single version with no dash) convert to min-max form
set from $versionspec set from $versionspec
if {![::punkboot::lib::tm_version_isvalid $from]} { if {![::punkboot::lib::tm_version_isvalid $from]} {
error "$errmsg '$versionpec'" error "$errmsg '$versionpec'"
@ -127,7 +130,7 @@ namespace eval ::punkboot::lib {
error "$errmsg '$versionspec'" error "$errmsg '$versionspec'"
} }
} else { } else {
# min- or min-max # min- or min-max
#validation and canonicalisation (strip leading zeroes from each segment, including either side of a or b) #validation and canonicalisation (strip leading zeroes from each segment, including either side of a or b)
set parts [split $versionspec -] ;#we expect only 2 parts set parts [split $versionspec -] ;#we expect only 2 parts
lassign $parts from to lassign $parts from to
@ -162,18 +165,18 @@ if {"::try" ni [info commands ::try]} {
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#Module loading from src/bootsupport or [pwd]/modules if pwd is a 'src' folder #Module loading from src/bootsupport or [pwd]/modules if pwd is a 'src' folder
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
#If there is a folder under the current directory, in the subpath src/bootsupport/modules which contains .tm files #If there is a folder under the current directory, in the subpath src/bootsupport/modules which contains .tm files
# - then it will attempt to preference these modules # - then it will attempt to preference these modules
# This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script
# and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables
set startdir [pwd] set startdir [pwd]
#we are focussed on pure-tcl libs/modules in bootsupport for now. #we are focussed on pure-tcl libs/modules in bootsupport for now.
#There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc
#REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries
# - we need to support that without binary downloads from repos unless the user explicitly asks for that. # - we need to support that without binary downloads from repos unless the user explicitly asks for that.
# - They may already be available in the vfs (or pointed to package paths) of the running executable. # - They may already be available in the vfs (or pointed to package paths) of the running executable.
# - todo: some user prompting regarding installs with platform-appropriate package managers # - todo: some user prompting regarding installs with platform-appropriate package managers
# - todo: some user prompting regarding building accelerators from source. # - todo: some user prompting regarding building accelerators from source.
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
set bootsupport_module_paths [list] set bootsupport_module_paths [list]
@ -209,7 +212,7 @@ if {[file tail $startdir] eq "src"} {
#todo - other src 'module' dirs.. #todo - other src 'module' dirs..
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] { foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] {
if {[file exists $p]} { if {[file exists $p]} {
lappend sourcesupport_module_paths $p lappend sourcesupport_module_paths $p
} }
} }
# -- -- -- # -- -- --
@ -219,7 +222,7 @@ if {[file tail $startdir] eq "src"} {
} }
} }
# -- -- -- # -- -- --
foreach p [list {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] { foreach p [list {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] {
if {[file exists $p]} { if {[file exists $p]} {
set sourcesupport_paths_exist 1 set sourcesupport_paths_exist 1
@ -228,7 +231,7 @@ if {[file tail $startdir] eq "src"} {
} }
if {$sourcesupport_paths_exist} { if {$sourcesupport_paths_exist} {
#launch from <projectdir/src is also likely to be common #launch from <projectdir/src is also likely to be common
# but we need to be loud about what's going on. # but we need to be loud about what's going on.
puts stderr "------------------------------------------------------------------" puts stderr "------------------------------------------------------------------"
puts stderr "Launched from within a folder ending in 'src'" puts stderr "Launched from within a folder ending in 'src'"
@ -238,7 +241,7 @@ if {[file tail $startdir] eq "src"} {
} }
# ------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
set package_paths_modified 0 set package_paths_modified 0
if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
set original_tm_list [tcl::tm::list] set original_tm_list [tcl::tm::list]
tcl::tm::remove {*}$original_tm_list tcl::tm::remove {*}$original_tm_list
@ -254,7 +257,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
} }
} }
set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk] ;#packages we set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk]
if {$support_contents_exist} { if {$support_contents_exist} {
#only forget all *unloaded* package names #only forget all *unloaded* package names
foreach pkg [package names] { foreach pkg [package names] {
@ -270,9 +273,9 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
package forget $pkg package forget $pkg
} }
} }
#tcl::tm::add {*}$original_tm_list {*}$bootsupport_module_paths {*}$sourcesupport_module_paths #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] #set ::auto_path [list {*}$original_auto_path {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths] set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
} }
puts "----> auto_path $::auto_path" puts "----> auto_path $::auto_path"
@ -281,18 +284,19 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
#package require Thread #package require Thread
# - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly.
# tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list
#These are strong dependencies #These are strong dependencies
package forget punk::mix package forget punk::mix
package forget punk::repo package forget punk::repo
package forget punkcheck package forget punkcheck
package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies
package require punk::mix package require punk::mix
package require punkcheck package require punkcheck
package require punk::lib package require punk::lib
package require punk::args
package require punk::ansi
set package_paths_modified 1 set package_paths_modified 1
@ -302,11 +306,12 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
set ::punkboot::pkg_requirements_found [list] set ::punkboot::pkg_requirements_found [list]
#we will treat 'package require <mver>.<etc>' (minbounded) as <mver>.<etc>-<mver+1> ie explicitly convert to corresponding bounded form #we will treat 'package require <mver>.<etc>' (minbounded) as <mver>.<etc>-<mver+1> ie explicitly convert to corresponding bounded form
#put some with leading zeros to test normalisation #put some with leading zeros to test normalisation
set ::punkboot::bootsupport_requirements [dict create\ set ::punkboot::bootsupport_requirements [dict create\
punk::repo [list version "00.01.01-"]\ punk::repo [list version "00.01.01-"]\
punk::mix [list version ""]\ punk::mix [list version ""]\
punk::ansi [list]\ punk::ansi [list]\
punk::args [list]\
overtype [list version "1.6.5-"]\ overtype [list version "1.6.5-"]\
punkcheck [list]\ punkcheck [list]\
fauxlink [list version "0.1.1-"]\ fauxlink [list version "0.1.1-"]\
@ -322,7 +327,7 @@ dict for {pkg pkginfo} $::punkboot::bootsupport_requirements {
if {![catch {::punkboot::lib::tm_version_required_canonical $ver} canonical]} { if {![catch {::punkboot::lib::tm_version_required_canonical $ver} canonical]} {
if {$canonical ne $ver} { if {$canonical ne $ver} {
dict set pkginfo version $canonical ;# plain ver mapped to min-max. min- and min-max and empty left as is dict set pkginfo version $canonical ;# plain ver mapped to min-max. min- and min-max and empty left as is
dict set ::punkboot::bootsupport_requirements $pkg $pkginfo dict set ::punkboot::bootsupport_requirements $pkg $pkginfo
} }
} else { } else {
puts stderr "punkboot::bootsupport_requirements - package $pkg has invalid version specification '$ver'" puts stderr "punkboot::bootsupport_requirements - package $pkg has invalid version specification '$ver'"
@ -331,9 +336,9 @@ dict for {pkg pkginfo} $::punkboot::bootsupport_requirements {
} else { } else {
#make sure each has a blank version entry if nothing was there. #make sure each has a blank version entry if nothing was there.
dict set pkginfo version "" dict set pkginfo version ""
dict set ::punkboot::bootsupport_requirements $pkg $pkginfo dict set ::punkboot::bootsupport_requirements $pkg $pkginfo
} }
} }
#Assert - our bootsupport_requirement version numbers should now be either empty or of the form min- or min-max #Assert - our bootsupport_requirement version numbers should now be either empty or of the form min- or min-max
#dict for {k v} $::punkboot::bootsupport_requirements { #dict for {k v} $::punkboot::bootsupport_requirements {
# puts "- $k $v" # puts "- $k $v"
@ -356,7 +361,7 @@ set ::punkboot::bootsupport_recommended [dict create\
# create an interp in which we hijack package command # create an interp in which we hijack package command
# This allows us to auto-gather some dependencies (not necessarily all and not necessarily strictly required) # This allows us to auto-gather some dependencies (not necessarily all and not necessarily strictly required)
# Note: even in a separate interp we could still possibly get side-effects if a package has compiled components - REVIEW # Note: even in a separate interp we could still possibly get side-effects if a package has compiled components - REVIEW
# Hopefully the only side-effect is that a subsequent load of the package will be faster... # Hopefully the only side-effect is that a subsequent load of the package will be faster...
# (punk boot is intended to operate without compiled components - but some could be pulled in by tcl modules if they're found) # (punk boot is intended to operate without compiled components - but some could be pulled in by tcl modules if they're found)
# (tcllibc is also highly desirable as the performance impact when not available can be dramatic.) # (tcllibc is also highly desirable as the performance impact when not available can be dramatic.)
# ... but if the binary is loaded with a different path name when we come to actually use it - there could be issues. # ... but if the binary is loaded with a different path name when we come to actually use it - there could be issues.
@ -378,7 +383,7 @@ proc ::punkboot::check_package_availability {args} {
#best effort at auto-determinining packages required (dependencies) based on top-level packages in the list. #best effort at auto-determinining packages required (dependencies) based on top-level packages in the list.
#Without fully parsing the package-loading Tcl scripts and examining all side-effects (an unlikely capability), #Without fully parsing the package-loading Tcl scripts and examining all side-effects (an unlikely capability),
# this is not going to be as accurate as the package developer providing a definitive list of which packages are required and which are optional. # this is not going to be as accurate as the package developer providing a definitive list of which packages are required and which are optional.
# 'optionality' is a contextual concept anyway depending on how the package is intended to be used. # 'optionality' is a contextual concept anyway depending on how the package is intended to be used.
# The package developer may consider a feature optional - but it may not be optional in a particular usecase. # The package developer may consider a feature optional - but it may not be optional in a particular usecase.
set bootsupport_requirements [lindex $args end] set bootsupport_requirements [lindex $args end]
@ -484,7 +489,7 @@ proc ::punkboot::check_package_availability {args} {
#should still distinguish: {pkgname {}} -valid vs {pkgname {{}}} due to empty string supplied in call - invalid - but leave for underlying package command to error on #should still distinguish: {pkgname {}} -valid vs {pkgname {{}}} due to empty string supplied in call - invalid - but leave for underlying package command to error on
set pkgrequest [list $pkgname $requirements_list] set pkgrequest [list $pkgname $requirements_list]
if {$pkgrequest ni $::test::pkg_requested} { if {$pkgrequest ni $::test::pkg_requested} {
lappend ::test::pkg_requested $pkgrequest lappend ::test::pkg_requested $pkgrequest
} }
# -- -- --- --- --- --- --- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- -- --- --- --- --- --- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
@ -507,13 +512,13 @@ proc ::punkboot::check_package_availability {args} {
} }
if {[llength $::test::pkg_stack]} { if {[llength $::test::pkg_stack]} {
set caller [lindex $::test::pkg_stack end] set caller [lindex $::test::pkg_stack end]
set required_by [dict get $pinfo required_by] set required_by [dict get $pinfo required_by]
if {$caller ni $required_by} { if {$caller ni $required_by} {
lappend required_by $caller lappend required_by $caller
} }
dict set pinfo required_by $required_by dict set pinfo required_by $required_by
} }
lappend ::test::pkg_stack $pkgname lappend ::test::pkg_stack $pkgname
#At this point we could short circuit if we've already classified this package/requirements combo as missing/broken from a previous require #At this point we could short circuit if we've already classified this package/requirements combo as missing/broken from a previous require
#review - there is some chance the exact pkg/requirements combo may succeed after an earlier failure if some package adjusted search paths.. #review - there is some chance the exact pkg/requirements combo may succeed after an earlier failure if some package adjusted search paths..
@ -527,23 +532,23 @@ proc ::punkboot::check_package_availability {args} {
#use our normalised requirements instead of original args #use our normalised requirements instead of original args
#if {[catch [list ::package_orig {*}$args] result]} {} #if {[catch [list ::package_orig {*}$args] result]} {}
if {[catch [list ::package_orig require $pkgname {*}$requirements_list] result]} { if {[catch [list ::package_orig require $pkgname {*}$requirements_list] result]} {
dict set pinfo testerror $result dict set pinfo testerror $result
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " \x1b\[93m$result\x1b\[m"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
#to determine the version that we attempted to load, #to determine the version that we attempted to load,
#- we need to look at 'pkg versions' vs -exact / ver / ver-ver (using package vsatisfies) #- we need to look at 'pkg versions' vs -exact / ver / ver-ver (using package vsatisfies)
if {![llength $versions]} { if {![llength $versions]} {
#no versions *and* we had an error - missing is our best guess. review. #no versions *and* we had an error - missing is our best guess. review.
#'package versions Tcl' never shows any results #'package versions Tcl' never shows any results
#so requests for old versions will show as missing not broken. #so requests for old versions will show as missing not broken.
#This is probably better anyway. #This is probably better anyway.
if {$pkgrequest ni $::test::pkg_missing} { if {$pkgrequest ni $::test::pkg_missing} {
@ -572,21 +577,21 @@ proc ::punkboot::check_package_availability {args} {
lappend selectable_versions $v lappend selectable_versions $v
} }
} else { } else {
#we are operating under 'package prefer' = latest #we are operating under 'package prefer' = latest
set selectable_versions $ordered_versions set selectable_versions $ordered_versions
} }
if {[llength $requirements_list]} { if {[llength $requirements_list]} {
#add one or no entry for each requirement. #add one or no entry for each requirement.
#pick highest at end #pick highest at end
set satisfiers [list] set satisfiers [list]
foreach requirement $requirements_list { foreach requirement $requirements_list {
foreach ver [lreverse $selectable_versions] { foreach ver [lreverse $selectable_versions] {
if {[package vsatisfies $ver $requirement]} { if {[package vsatisfies $ver $requirement]} {
lappend satisfiers $ver lappend satisfiers $ver
break break
} }
} }
} }
if {[llength $satisfiers]} { if {[llength $satisfiers]} {
set satisfiers [lsort -command {::package_orig vcompare} $satisfiers] set satisfiers [lsort -command {::package_orig vcompare} $satisfiers]
@ -622,7 +627,7 @@ proc ::punkboot::check_package_availability {args} {
if {![catch {::package_orig files Tcl} ]} { if {![catch {::package_orig files Tcl} ]} {
#tcl9 (also some 8.6/8.7) has 'package files' subcommand. #tcl9 (also some 8.6/8.7) has 'package files' subcommand.
#unfortunately, in some cases (e.g md5 when no accelerators available) this can be a huge list (1000+) showing all scanned pkgIndex.tcl files from unrelated packages. #unfortunately, in some cases (e.g md5 when no accelerators available) this can be a huge list (1000+) showing all scanned pkgIndex.tcl files from unrelated packages.
#We expect this to be fixed - but early Tcl9 (and some 8.6/8.7) versions may persist and have this behaviour #We expect this to be fixed - but early Tcl9 (and some 8.6/8.7) versions may persist and have this behaviour
#see: https://core.tcl-lang.org/tcl/tktview/209fd9adce #see: https://core.tcl-lang.org/tcl/tktview/209fd9adce
set all_files [::package_orig files $pkgname] set all_files [::package_orig files $pkgname]
#some arbitrary threshold? REVIEW #some arbitrary threshold? REVIEW
@ -637,7 +642,7 @@ proc ::punkboot::check_package_availability {args} {
dict set pinfo packagefiles {} ;#default dict set pinfo packagefiles {} ;#default
#there are all sorts of scripts, so this is not predictably structured #there are all sorts of scripts, so this is not predictably structured
#e.g using things like apply #e.g using things like apply
#we will attempt to get a trailing source .. <file> #we will attempt to get a trailing source .. <file>
set parts [split [string trim $ifneeded_script] {;}] set parts [split [string trim $ifneeded_script] {;}]
set trimparts [list] set trimparts [list]
foreach p $parts { foreach p $parts {
@ -648,7 +653,7 @@ proc ::punkboot::check_package_availability {args} {
if {$last_with_text ne "" && [regexp -- {\S+$} $last_with_text lastword]} { if {$last_with_text ne "" && [regexp -- {\S+$} $last_with_text lastword]} {
#if it's a file or dir - close enough (?) #if it's a file or dir - close enough (?)
#e.g tcllibc uses apply and the last entry is actuall a folder used to find the file.. #e.g tcllibc uses apply and the last entry is actuall a folder used to find the file..
#we aren't brave enough to try to work out the actual file(s) #we aren't brave enough to try to work out the actual file(s)
if {[file exists $lastword]} { if {[file exists $lastword]} {
dict set pinfo packagefiles $lastword dict set pinfo packagefiles $lastword
} }
@ -662,10 +667,10 @@ proc ::punkboot::check_package_availability {args} {
return [uplevel 1 [list ::package_orig {*}$args]] return [uplevel 1 [list ::package_orig {*}$args]]
} }
} }
set ::test::pkg_stack [list] set ::test::pkg_stack [list]
catch {::package_orig require zzz-non-existant} ;#scan so we get 'package versions' results catch {::package_orig require zzz-non-existant} ;#scan so we get 'package versions' results
dict for {pkg pkgdict} $::test::bootsupport_requirements { dict for {pkg pkgdict} $::test::bootsupport_requirements {
#set nsquals [namespace qualifiers $pkg] #set nsquals [namespace qualifiers $pkg]
#if {$nsquals ne ""} { #if {$nsquals ne ""} {
# catch {::package_orig require ${nsquals}::zzz-non-existant} ;#force scan of every level encountered # catch {::package_orig require ${nsquals}::zzz-non-existant} ;#force scan of every level encountered
@ -690,7 +695,7 @@ proc ::punkboot::check_package_availability {args} {
# set ver [package provide $pkg] # set ver [package provide $pkg]
# if {$ver eq ""} { # if {$ver eq ""} {
# #puts stderr "missing pkg: $pkg" # #puts stderr "missing pkg: $pkg"
# lappend ::test::pkg_missing $pkg # lappend ::test::pkg_missing $pkg
# } else { # } else {
# if {[string tolower $pkg] eq "tcl"} { # if {[string tolower $pkg] eq "tcl"} {
# #ignore # #ignore
@ -1180,17 +1185,17 @@ if {$::punkboot::command eq "check"} {
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
foreach fld [tcl::tm::list] { foreach fld [tcl::tm::list] {
if {[file exists $fld]} { if {[file exists $fld]} {
puts stdout " $fld" puts stdout " $fld"
} else { } else {
puts stdout " $fld (not present)" puts stdout " $fld (not present)"
} }
} }
puts stdout "- auto_path" puts stdout "- auto_path"
foreach fld $::auto_path { foreach fld $::auto_path {
if {[file exists $fld]} { if {[file exists $fld]} {
puts stdout " $fld" puts stdout " $fld"
} else { } else {
puts stdout " $fld (not present)" puts stdout " $fld (not present)"
} }
} }
flush stdout flush stdout
@ -1283,22 +1288,22 @@ if {$::punkboot::command eq "info"} {
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
puts stdout "- vendorlib folders: ([llength $vendorlibfolders])" puts stdout "- vendorlib folders: ([llength $vendorlibfolders])"
foreach fld $vendorlibfolders { foreach fld $vendorlibfolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
puts stdout "- vendormodule folders: ([llength $vendormodulefolders])" puts stdout "- vendormodule folders: ([llength $vendormodulefolders])"
foreach fld $vendormodulefolders { foreach fld $vendormodulefolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot] set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot]
puts stdout "- source module paths: [llength $source_module_folderlist]" puts stdout "- source module paths: [llength $source_module_folderlist]"
foreach fld $source_module_folderlist { foreach fld $source_module_folderlist {
puts stdout " $fld" puts stdout " $fld"
} }
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*] set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib lappend projectlibfolders lib
puts stdout "- source libary paths: [llength $projectlibfolders]" puts stdout "- source libary paths: [llength $projectlibfolders]"
foreach fld $projectlibfolders { foreach fld $projectlibfolders {
puts stdout " src/$fld" puts stdout " src/$fld"
} }
if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} { if {[punk::repo::find_fossil $scriptfolder] eq $projectroot} {
set vc "fossil" set vc "fossil"
@ -1389,10 +1394,9 @@ if {$::punkboot::command eq "vendorupdate"} {
#todo vendor/lib #todo vendor/lib
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*] set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*]
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
lappend vendormodulefolders vendormodules #lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders { foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} {
lassign [split $vf _] _vm tclx lassign [split $vf _] _vm tclx
if {$tclx ne ""} { if {$tclx ne ""} {
set which _$tclx set which _$tclx
@ -1481,7 +1485,6 @@ if {$::punkboot::command eq "vendorupdate"} {
} else { } else {
puts stderr "No config at $vendor_config - nothing configured to update" puts stderr "No config at $vendor_config - nothing configured to update"
} }
}
} }
} }
@ -1508,105 +1511,102 @@ if {$::punkboot::command eq "bootsupport"} {
set bootsupport_modules [list] ;#variable populated by include_modules.config file - review set bootsupport_modules [list] ;#variable populated by include_modules.config file - review
set sourcefolder $projectroot/src set sourcefolder $projectroot/src
set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules_tcl*] set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules modules_tcl*]
lappend bootmodulefolders modules
foreach bm $bootmodulefolders { foreach bm $bootmodulefolders {
if {[file exists $sourcefolder/bootsupport/$bm]} { lassign [split $bm _] _bm tclx
lassign [split $bm _] _bm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
set which ""
}
set bootsupport_config $projectroot/src/bootsupport/modules$which/include_modules.config ;#
if {[file exists $bootsupport_config]} {
set targetroot $projectroot/src/bootsupport/modules$which
source $bootsupport_config ;#populate $bootsupport_modules with project-specific list
if {![llength $bootsupport_modules]} {
puts stderr "bootsupport/modules$which - No local bootsupport modules configured for updating"
} else { } else {
set which ""
}
set bootsupport_config $projectroot/src/bootsupport/modules$which/include_modules.config ;#
if {[file exists $bootsupport_config]} {
set targetroot $projectroot/src/bootsupport/modules$which
source $bootsupport_config ;#populate $bootsupport_modules with project-specific list
if {![llength $bootsupport_modules]} {
puts stderr "bootsupport/modules$which - No local bootsupport modules configured for updating"
} else {
if {[catch { if {[catch {
#---------- #----------
set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck] set boot_installer [punkcheck::installtrack new make.tcl $projectroot/src/bootsupport/.punkcheck]
$boot_installer set_source_target $projectroot $projectroot/src/bootsupport $boot_installer set_source_target $projectroot $projectroot/src/bootsupport
set boot_event [$boot_installer start_event {-make_step bootsupport}] set boot_event [$boot_installer start_event {-make_step bootsupport}]
#---------- #----------
} errM]} { } errM]} {
puts stderr "Unable to use punkcheck for bootsupport error: $errM" puts stderr "Unable to use punkcheck for bootsupport error: $errM"
set boot_event "" set boot_event ""
} }
foreach {relpath modulematch} $bootsupport_modules { foreach {relpath modulematch} $bootsupport_modules {
set modulematch [string trim $modulematch :] set modulematch [string trim $modulematch :]
set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]] set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]]
set srclocation [file join $projectroot $relpath $module_subpath] set srclocation [file join $projectroot $relpath $module_subpath]
#puts stdout "$relpath $modulematch $module_subpath $srclocation" #puts stdout "$relpath $modulematch $module_subpath $srclocation"
if {[string first - $modulematch]} { if {[string first - $modulematch]} {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm] set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm]
} else { } else {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm] set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm]
} }
if {![llength $pkgmatches]} { if {![llength $pkgmatches]} {
puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation" puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation"
continue continue
} }
set modulematch_is_glob [regexp {[*?\[\]]} $modulematch] set modulematch_is_glob [regexp {[*?\[\]]} $modulematch]
if {!$modulematch_is_glob} { if {!$modulematch_is_glob} {
#if modulematch was specified without globs - only copy latest #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 #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 pkgmatches [lsort -command modfile_sort $pkgmatches]
set latestfile [lindex $pkgmatches end] set latestfile [lindex $pkgmatches end]
#set latestver [lindex [split [file rootname $latestfile] -] 1] #set latestver [lindex [split [file rootname $latestfile] -] 1]
set copy_files $latestfile set copy_files $latestfile
} else { } else {
#globs in modulematch - may be different packages matched by glob - copy all versions of matches #globs in modulematch - may be different packages matched by glob - copy all versions of matches
#review #review
set copy_files $pkgmatches set copy_files $pkgmatches
} }
foreach cfile $copy_files { foreach cfile $copy_files {
set srcfile [file join $srclocation $cfile] set srcfile [file join $srclocation $cfile]
set tgtfile [file join $targetroot $module_subpath $cfile] set tgtfile [file join $targetroot $module_subpath $cfile]
if {$boot_event ne ""} { if {$boot_event ne ""} {
#---------- #----------
$boot_event targetset_init INSTALL $tgtfile $boot_event targetset_init INSTALL $tgtfile
$boot_event targetset_addsource $srcfile $boot_event targetset_addsource $srcfile
#---------- #----------
if {\ if {\
[llength [dict get [$boot_event targetset_source_changes] changed]]\ [llength [dict get [$boot_event targetset_source_changes] changed]]\
|| [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\ || [llength [$boot_event get_targets_exist]] < [llength [$boot_event get_targets]]\
} { } {
file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists
$boot_event targetset_started $boot_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile"
if {[catch { if {[catch {
file copy -force $srcfile $tgtfile file copy -force $srcfile $tgtfile
} errM]} { } errM]} {
$boot_event targetset_end FAILED $boot_event targetset_end FAILED
} else {
$boot_event targetset_end OK
}
# -- --- --- --- --- ---
} else { } else {
puts -nonewline stderr "." $boot_event targetset_end OK
$boot_event targetset_end SKIPPED
} }
$boot_event end # -- --- --- --- --- ---
} else { } else {
file copy -force $srcfile $tgtfile puts -nonewline stderr "."
$boot_event targetset_end SKIPPED
} }
$boot_event end
} else {
file copy -force $srcfile $tgtfile
} }
} }
if {$boot_event ne ""} {
puts \n
$boot_event destroy
$boot_installer destroy
}
} }
if {$boot_event ne ""} {
puts \n
$boot_event destroy
$boot_installer destroy
}
} }
} }
} }
} }
@ -1699,59 +1699,53 @@ if {$::punkboot::command ni {project modules vfs}} {
#install src vendor contents (from version controlled src folder) to base of project (same target folders as our own src/modules etc ie to paths that go on the auto_path and in tcl::tm::list) #install src vendor contents (from version controlled src folder) to base of project (same target folders as our own src/modules etc ie to paths that go on the auto_path and in tcl::tm::list)
if {$::punkboot::command in {project modules}} { if {$::punkboot::command in {project modules}} {
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*] set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
lappend vendorlibfolders vendorlib
foreach lf $vendorlibfolders {
if {[file exists $sourcefolder/$lf]} {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
set which ""
}
set target_lib_folder $projectroot/lib$which
file mkdir $projectroot/lib$which
#exclude README.md from source folder - but only the root one
#-antiglob_paths takes relative patterns e.g
# */test.txt will only match test.txt exactly one level deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# **/test.txt will match at any level below the root (but not in the root)
set antipaths [list\
README.md\
]
puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
}
if {![llength $vendorlibfolders]} {
puts stderr "VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found."
}
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders { foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} { lassign [split $vf _] _vm tclx
lassign [split $vf _] _vm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
} else { set which ""
set which ""
}
set target_module_folder $projectroot/modules$which
file mkdir $target_module_folder
#install .tm *and other files*
puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} }
set target_module_folder $projectroot/modules$which
file mkdir $target_module_folder
#install .tm *and other files*
puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} }
if {![llength $vendormodulefolders]} { if {![llength $vendormodulefolders]} {
puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found." puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found."
} }
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib vendorlib_tcl*]
foreach lf $vendorlibfolders {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
set which ""
}
set target_lib_folder $projectroot/lib$which
file mkdir $projectroot/lib$which
#exclude README.md from source folder - but only the root one
#-antiglob_paths takes relative patterns e.g
# */test.txt will only match test.txt exactly one level deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# **/test.txt will match at any level below the root (but not in the root)
set antipaths [list\
README.md\
]
puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
if {![llength $vendorlibfolders]} {
puts stderr "VENDORLIB: No src/vendorlib or src/vendorlib_tcl* folder found."
}
######################################################## ########################################################
#templates #templates
#e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync #e.g The default project layout is mainly folder structure and readme files - but has some scripts developed under the main src that we want to sync
@ -1760,10 +1754,10 @@ if {$::punkboot::command in {project modules}} {
set old_layout_update_list [list\ set old_layout_update_list [list\
[list project $sourcefolder/modules/punk/mix/templates]\ [list project $sourcefolder/modules/punk/mix/templates]\
[list basic $sourcefolder/mixtemplates]\ [list basic $sourcefolder/mixtemplates]\
] ]
set layout_bases [list\ set layout_bases [list\
$sourcefolder/project_layouts/custom/_project\ $sourcefolder/project_layouts/custom/_project\
] ]
foreach layoutbase $layout_bases { foreach layoutbase $layout_bases {
if {![file exists $layoutbase]} { if {![file exists $layoutbase]} {
@ -1823,27 +1817,25 @@ if {$::punkboot::command in {project modules}} {
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*] set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib lappend projectlibfolders lib
foreach lf $projectlibfolders { foreach lf $projectlibfolders {
if {[file exists $sourcefolder/$lf]} { lassign [split $lf _] _vm tclx
lassign [split $lf _] _vm tclx if {$tclx ne ""} {
if {$tclx ne ""} { set which _$tclx
set which _$tclx } else {
} else { set which ""
set which "" }
} set target_lib_folder $projectroot/lib$which
set target_lib_folder $projectroot/lib$which file mkdir $projectroot/lib$which
file mkdir $projectroot/lib$which #exclude README.md from source folder - but only the root one
#exclude README.md from source folder - but only the root one #-antiglob_paths takes relative patterns e.g
#-antiglob_paths takes relative patterns e.g # */test.txt will only match test.txt exactly one level deep.
# */test.txt will only match test.txt exactly one level deep. # */*/*.foo will match any path ending in .foo that is exactly 2 levels deep.
# */*/*.foo will match any path ending in .foo that is exactly 2 levels deep. # **/test.txt will match at any level below the root (but not in the root)
# **/test.txt will match at any level below the root (but not in the root) set antipaths [list\
set antipaths [list\ README.md\
README.md\ ]
] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)"
puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] puts stdout [punkcheck::summarize_install_resultdict $resultdict]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
} }
if {![llength $projectlibfolders]} { if {![llength $projectlibfolders]} {
puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found." puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found."
@ -2355,7 +2347,7 @@ foreach vfstail $vfs_tails {
} else { } else {
lappend runtimes $matchrt lappend runtimes $matchrt
} }
} }
} }
#assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config #assert $runtimes is a list of executable names suffixed with .exe if on windows - whether or not specified with .exe in the mapvfs.config

259
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/argp-0.2.tm vendored

@ -0,0 +1,259 @@
# Tcl parser for optional arguments in function calls and
# commandline arguments
#
# (c) 2001 Bastien Chevreux
# Index of exported commands
# - argp::registerArgs
# - argp::setArgDefaults
# - argp::setArgsNeeded
# - argp::parseArgs
# Internal commands
# - argp::CheckValues
# See end of file for an example on how to use
package provide argp 0.2
namespace eval argp {
variable Optstore
variable Opttypes {
boolean integer double string
}
namespace export {[a-z]*}
}
proc argp::registerArgs { func arglist } {
variable Opttypes
variable Optstore
set parentns [string range [uplevel 1 [list namespace current]] 2 end]
if { $parentns != "" } {
append caller $parentns :: $func
} else {
set caller $func
}
set cmangled [string map {:: _} $caller]
#puts $parentns
#puts $caller
#puts $cmangled
set Optstore(keys,$cmangled) {}
set Optstore(deflist,$cmangled) {}
set Optstore(argneeded,$cmangled) {}
foreach arg $arglist {
foreach {opt type default allowed} $arg {
set optindex [lsearch -glob $Opttypes $type*]
if { $optindex < 0} {
return -code error "$caller, unknown type $type while registering arguments.\nAllowed types: [string trim $Opttypes]"
}
set type [lindex $Opttypes $optindex]
lappend Optstore(keys,$cmangled) $opt
set Optstore(type,$opt,$cmangled) $type
set Optstore(default,$opt,$cmangled) $default
set Optstore(allowed,$opt,$cmangled) $allowed
lappend Optstore(deflist,$cmangled) $opt $default
}
}
if { [catch {CheckValues $caller $cmangled $Optstore(deflist,$cmangled)} res]} {
return -code error "Error in declaration of optional arguments.\n$res"
}
}
proc argp::setArgDefaults { func arglist } {
variable Optstore
set parentns [string range [uplevel 1 [list namespace current]] 2 end]
if { $parentns != "" } {
append caller $parentns :: $func
} else {
set caller $func
}
set cmangled [string map {:: _} $caller]
if {![info exists Optstore(deflist,$cmangled)]} {
return -code error "Arguments for $caller not registered yet."
}
set Optstore(deflist,$cmangled) {}
foreach {opt default} $arglist {
if {![info exists Optstore(default,$opt,$cmangled)]} {
return -code error "$caller, unknown option $opt, must be one of: $Optstore(keys,$cmangled)"
}
set Optstore(default,$opt,$cmangled) $default
}
# set the new defaultlist
foreach opt $Optstore(keys,$cmangled) {
lappend Optstore(deflist,$cmangled) $opt $Optstore(default,$opt,$cmangled)
}
}
proc argp::setArgsNeeded { func arglist } {
variable Optstore
set parentns [string range [uplevel 1 [list namespace current]] 2 end]
if { $parentns != "" } {
append caller $parentns :: $func
} else {
set caller $func
}
set cmangled [string map {:: _} $caller]
#append caller $parentns :: $func
#set cmangled ${parentns}_$func
if {![info exists Optstore(deflist,$cmangled)]} {
return -code error "Arguments for $caller not registered yet."
}
set Optstore(argneeded,$cmangled) {}
foreach opt $arglist {
if {![info exists Optstore(default,$opt,$cmangled)]} {
return -code error "$caller, unknown option $opt, must be one of: $Optstore(keys,$cmangled)"
}
lappend Optstore(argneeded,$cmangled) $opt
}
}
proc argp::parseArgs { args } {
variable Optstore
if {[llength $args] == 0} {
upvar args a opts o
} else {
upvar args a [lindex $args 0] o
}
if { [ catch { set caller [lindex [info level -1] 0]}]} {
set caller "main program"
set cmangled ""
} else {
set cmangled [string map {:: _} $caller]
}
if {![info exists Optstore(deflist,$cmangled)]} {
return -code error "Arguments for $caller not registered yet."
}
# set the defaults
array set o $Optstore(deflist,$cmangled)
# but unset the needed arguments
foreach key $Optstore(argneeded,$cmangled) {
catch { unset o($key) }
}
foreach {key val} $a {
if {![info exists Optstore(type,$key,$cmangled)]} {
return -code error "$caller, unknown option $key, must be one of: $Optstore(keys,$cmangled)"
}
switch -exact -- $Optstore(type,$key,$cmangled) {
boolean -
integer {
if { $val == "" } {
return -code error "$caller, $key empty string is not $Optstore(type,$key,$cmangled) value."
}
if { ![string is $Optstore(type,$key,$cmangled) $val]} {
return -code error "$caller, $key $val is not $Optstore(type,$key,$cmangled) value."
}
}
double {
if { $val == "" } {
return -code error "$caller, $key empty string is not double value."
}
if { ![string is double $val]} {
return -code error "$caller, $key $val is not double value."
}
if { [string is integer $val]} {
set val [expr {$val + .0}]
}
}
default {
}
}
set o($key) $val
}
foreach key $Optstore(argneeded,$cmangled) {
if {![info exists o($key)]} {
return -code error "$caller, needed argument $key was not given."
}
}
if { [catch { CheckValues $caller $cmangled [array get o]} err]} {
return -code error $err
}
return
}
proc argp::CheckValues { caller cmangled checklist } {
variable Optstore
#puts "Checking $checklist"
foreach {key val} $checklist {
if { [llength $Optstore(allowed,$key,$cmangled)] > 0 } {
switch -exact -- $Optstore(type,$key,$cmangled) {
string {
if { [lsearch $Optstore(allowed,$key,$cmangled) $val] < 0} {
return -code error "$caller, $key $val is not in allowed values: $Optstore(allowed,$key,$cmangled)"
}
}
double -
integer {
set found 0
foreach range $Optstore(allowed,$key,$cmangled) {
if {[llength $range] == 1} {
if { $val == [lindex $range 0] } {
set found 1
break
}
} elseif {[llength $range] == 2} {
set low [lindex $range 0]
set high [lindex $range 1]
if { ![string is integer $low] \
&& [string compare "-" $low] != 0} {
return -code error "$caller, $key of type $Optstore(type,$key,$cmangled) has a lower value range that is not integer and not \u00b4-\u00b4: $range"
}
if { ![string is integer $high] \
&& [string compare "+" $high] != 0} {
return -code error "$caller, $key of type $Optstore(type,$key,$cmangled) has a upper value range that is not integer and not \u00b4+\u00b4: $range"
}
if {[string compare "-" $low] == 0} {
if { [string compare "+" $high] == 0 \
|| $val <= $high } {
set found 1
break
}
}
if { $val >= $low } {
if {[string compare "+" $high] == 0 \
|| $val <= $high } {
set found 1
break
}
}
} else {
return -code error "$caller, $key of type $Optstore(type,$key,$cmangled) has an allowed value range containing more than 2 elements: $range"
}
}
if { $found == 0 } {
return -code error "$caller, $key $val is not covered by allowed ranges: $Optstore(allowed,$key,$cmangled)"
}
}
}
}
}
}

568
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/argparsingtest-0.1.0.tm vendored

@ -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]

514
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/commandstack-0.3.tm vendored

@ -0,0 +1,514 @@
#JMN 2021 - Public Domain
#cooperative command renaming
#
# REVIEW 2024 - code was originally for specific use in packageTrace
# - code should be reviewed for more generic utility.
# - API is obscure and undocumented.
# - unclear if intention was only for builtins
# - consider use of newer 'info cmdtype' - (but need also support for safe interps)
# - oo dispatch features may be a better implementation - especially for allowing undoing command renames in the middle of a stack.
# - document that replacement command should use 'commandstack::get_next_command <cmd> <renamer>' for delegating to command as it was prior to rename
#changes:
#2024
# - mungecommand to support namespaced commands
# - fix mistake - hardcoded _originalcommand_package -> _originalcommand_<mungedcommand>
#2021-09-18
# - initial version
# - e.g Support cooperation between packageSuppress and packageTrace which both rename the package command
# - They need to be able to load and unload in any order.
#
#strive for no other package dependencies here.
namespace eval commandstack {
variable all_stacks
variable debug
set debug 0
variable known_renamers [list ::packagetrace ::packageSuppress]
if {![info exists all_stacks]} {
#don't wipe it
set all_stacks [dict create]
}
}
namespace eval commandstack::util {
#note - we can't use something like md5 to ID proc body text because we don't want to require additional packages.
#We could store the full text of the body to compare - but we need to identify magic strings from cooperating packages such as packageTrace
#A magic comment was chosen as the identifying method.
#The string IMPLEMENTOR_*! is searched for where the text between _ and ! is the name of the package that implemented the proc.
#return unspecified if the command is a proc with a body but no magic comment ID
#return unknown if the command doesn't have a proc body to analyze
#otherwise return the package name identified in the magic comment
proc get_IMPLEMENTOR {command} {
#assert - command has already been resolved to a namespace ie fully qualified
if {[llength [info procs $command]]} {
#look for *IMPLEMENTOR_*!
set prefix IMPLEMENTOR_
set suffix "!"
set body [uplevel 1 [list info body $command]]
if {[string match "*$prefix*$suffix*" $body]} {
set prefixposn [string first "$prefix" $body]
set pkgposn [expr {$prefixposn + [string length $prefix]}]
#set suffixposn [string first $suffix [string range $body $pkgposn $pkgposn+60]]
set suffixposn [string first $suffix $body $pkgposn]
return [string range $body $pkgposn $suffixposn-1]
} else {
return unspecified
}
} else {
if {[info commands tcl::info::cmdtype] ne ""} {
#tcl9 and maybe some tcl 8.7s ?
switch -- [tcl::info::cmdtype $command] {
native {
return builtin
}
default {
return undetermined
}
}
} else {
return undetermined
}
}
}
}
namespace eval commandstack::renamed_commands {}
namespace eval commandstack::temp {} ;#where we create proc initially before renaming into place
namespace eval commandstack {
namespace export {[a-z]*}
proc help {} {
return {
}
}
proc debug {{on_off {}}} {
variable debug
if {$on_off eq ""} {
return $debug
} else {
if {[string is boolean -strict $debug]} {
set debug [expr {$on_off && 1}]
return $debug
}
}
}
proc get_stack {command} {
variable all_stacks
set command [uplevel 1 [list namespace which $command]]
if {[dict exists $all_stacks $command]} {
return [dict get $all_stacks $command]
} else {
return [list]
}
}
#get the implementation to which the renamer (renamer is usually calling namespace) originally renamed it, or the implementation it now points to.
#review - performance impact. Possible to use oo for faster dispatch whilst allowing stack re-orgs?
#e.g if renaming builtin 'package' - this command is generally called 'a lot'
proc get_next_command {command renamer tokenid} {
variable all_stacks
if {[dict exists $all_stacks $command]} {
set stack [dict get $all_stacks $command]
set posn [lsearch -index 1 $stack [list $command $renamer $tokenid]]
if {$posn > -1} {
set record [lindex $stack $posn]
return [dict get $record implementation]
} else {
error "(commandstack::get_next_command) ERROR: unable to determine next command for '$command' using token: $command $renamer $tokenid"
}
} else {
return $command
}
}
proc basecall {command args} {
variable all_stacks
set command [uplevel 1 [list namespace which $command]]
if {[dict exists $all_stacks $command]} {
set stack [dict get $all_stacks $command]
if {[llength $stack]} {
set rec1 [lindex $stack 0]
tailcall [dict get $rec1 implementation] {*}$args
} else {
tailcall $command {*}$args
}
} else {
tailcall $command {*}$args
}
}
#review.
#<renamer> defaults to calling namespace - but can be arbitrary string
proc rename_command {args} {
#todo: consider -forcebase 1 or similar to allow this rename to point to bottom of stack (original command) bypassing existing renames
# - need to consider that upon removing, that any remaining rename that was higher on the stack should not also be diverted to the base - but rather to the next lower in the stack
#
if {[lindex $args 0] eq "-renamer"} {
set renamer [lindex $args 1]
set arglist [lrange $args 2 end]
} else {
set renamer ""
set arglist $args
}
if {[llength $arglist] != 3} {
error "commandstack::rename_command usage: rename_command ?-renamer <string>? command procargs procbody"
}
lassign $arglist command procargs procbody
set command [uplevel 1 [list namespace which $command]]
set mungedcommand [string map {:: _ns_} $command]
set mungedrenamer [string map {:: _ns_} $renamer]
variable all_stacks
variable known_renamers
variable renamer_command_tokens ;#monotonically increasing int per <mungedrenamer>::<mungedcommand> representing number of renames ever done.
if {$renamer eq ""} {
set renamer [uplevel 1 [list namespace current]]
}
if {$renamer ni $known_renamers} {
lappend known_renamers $renamer
dict set renamer_command_tokens [list $renamer $command] 0
}
#TODO - reduce emissions to stderr - flag for debug?
#e.g packageTrace and packageSuppress packages use this convention.
set nextinfo [uplevel 1 [list\
apply {{command renamer procbody} {
#todo - munge dash so we can make names in renamed_commands separable
# {- _dash_} ?
set mungedcommand [string map {:: _ns_} $command]
set mungedrenamer [string map {:: _ns_} $renamer]
set tokenid [lindex [dict incr renamer_command_tokens [list $renamer $command]] 1]
set next_target ::commandstack::renamed_commands::${mungedcommand}-original-$mungedrenamer-$tokenid ;#default is to assume we are the only one playing around with it, but we'll check for known associates too.
set do_rename 0
if {[llength [info procs $command]] || [llength [info commands $next_target]]} {
#$command is not the standard builtin - something has replaced it, could be ourself.
set next_implementor [::commandstack::util::get_IMPLEMENTOR $command]
set munged_next_implementor [string map {:: _ns_} $next_implementor]
#if undetermined/unspecified it could be the latest renamer on the stack - but we can't know for sure something else didn't rename it.
if {[dict exists $::commandstack::all_stacks $command]} {
set comstacks [dict get $::commandstack::all_stacks $command]
} else {
set comstacks [list]
}
set this_renamer_previous_entries [lsearch -all -index 3 $comstacks $renamer] ;#index 3 is value for second dict entry - (value for key 'renamer')
if {[llength $this_renamer_previous_entries]} {
if {$next_implementor eq $renamer} {
#previous renamer was us. Rather than assume our job is done.. compare the implementations
#don't rename if immediate predecessor is same code.
#set topstack [lindex $comstacks end]
#set next_impl [dict get $topstack implementation]
set current_body [info body $command]
lassign [commandstack::lib::split_body $current_body] _ current_code
set current_code [string trim $current_code]
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 [::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 "----------"
puts stdout "$current_code"
puts stdout "----------"
puts stdout "$new_code"
puts stdout "----------"
set next_target ::commandstack::renamed_commands::${mungedcommand}-${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
}
} else {
puts stderr "(commandstack::rename_command) WARNING - renamer '$renamer' has already renamed the '$command' command, but is not immediate predecessor - proceeding anyway... (untested)"
puts stderr
set next_target ::commandstack::renamed_commands::${mungedcommand}-${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
}
} elseif {$next_implementor in $::commandstack::known_renamers} {
set next_target ::commandstack::renamed_commands::${mungedcommand}-${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
} elseif {$next_implementor in {builtin}} {
#native/builtin could still have been renamed
set next_target ::commandstack::renamed_commands::${mungedcommand}_${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
} elseif {$next_implementor in {unspecified undetermined}} {
#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 {
puts stderr "(commandstack::rename_command) Warning - pkg:'$next_implementor' has renamed the '$command' command. Attempting to cooperate. (untested)"
set next_target ::commandstack::renamed_commands::${mungedcommand}_${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
}
} else {
#_originalcommand_<mungedcommand>
#assume builtin/original
set next_implementor original
#rename $command $next_target
set do_rename 1
}
#There are of course other ways in which $command may have been renamed - but we can't detect.
set token [list $command $renamer $tokenid]
return [dict create next_target $next_target next_implementor $next_implementor token $token do_rename $do_rename]
} } $command $renamer $procbody]
]
variable debug
if {$debug} {
if {[dict exists $all_stacks $command]} {
set stack [dict get $all_stacks $command]
puts stderr "(commandstack::rename_command) Subsequent rename of command '$command'. (previous renames: [llength $stack]). Renaming to [dict get $nextinfo next_target]"
} else {
#assume this is the original
puts stderr "(commandstack::rename_command) 1st detected rename of command '$command'. Renaming to [dict get $nextinfo next_target]"
}
}
#token is always first dict entry. (Value needs to be searched with lsearch -index 1 )
#renamer is always second dict entry (Value needs to be searched with lsearch -index 3)
set new_record [dict create\
token [dict get $nextinfo token]\
renamer $renamer\
next_implementor [dict get $nextinfo next_implementor]\
next_getter [list ::commandstack::get_next_command {*}[dict get $nextinfo token]]\
implementation [dict get $nextinfo next_target]\
]
if {![dict get $nextinfo do_rename]} {
#review
puts stderr "no rename performed"
return [dict create implementation ""]
}
catch {rename ::commandstack::temp::testproc ""}
set nextinit [string map [list %command% $command %renamer% $renamer %next_getter% [dict get $new_record next_getter] %original_implementation% [dict get $new_record implementation]] {
#IMPLEMENTOR_%renamer%! (mechanism: 'commandstack::rename_command -renamer %renamer% %command% <procargs> <procbody> )
set COMMANDSTACKNEXT_ORIGINAL %original_implementation% ;#informational/debug for overriding proc.
set COMMANDSTACKNEXT [%next_getter%]
#<commandstack_separator>#
}]
set final_procbody "$nextinit$procbody"
#build the proc at a temp location so that if it raises an error we don't adjust the stack or replace the original command
#(e.g due to invalid argument specifiers)
proc ::commandstack::temp::testproc $procargs $final_procbody
uplevel 1 [list rename $command [dict get $nextinfo next_target]]
uplevel 1 [list rename ::commandstack::temp::testproc $command]
dict lappend all_stacks $command $new_record
return $new_record
}
#todo - concept of 'pop' for renamer. Remove topmost entry specific to the renamer
#todo - removal by token to allow renamer to have multiple entries for one command but to remove one that is not the topmost
#todo - removal of all entries pertaining to a particular renamer
#todo - allow restore to bottom-most implementation (original) - regardless of what renamers have cooperated in the stack?
#remove by token, or by commandname if called from same context as original rename_command
#If only a commandname is supplied, and there were multiple renames from the same context (same -renamer) only the topmost is removed.
#A call to remove_rename with no token or renamer, and from a namespace context which didn't perform a rename will not remove anything.
#similarly a nonexistant token or renamer will not remove anything and will just return the current stack
proc remove_rename {token_or_command} {
if {[llength $token_or_command] == 3} {
#is token
lassign $token_or_command command renamer tokenid
} elseif {[llength $token_or_command] == 2} {
#command and renamer only supplied
lassign $token_or_command command renamer
set tokenid ""
} elseif {[llength $token_or_command] == 1} {
#is command name only
set command $token_or_command
set renamer [uplevel 1 [list namespace current]]
set tokenid ""
}
set command [uplevel 1 [list namespace which $command]]
variable all_stacks
variable known_renamers
if {$renamer ni $known_renamers} {
error "(commandstack::remove_rename) ERROR: renamer $renamer not in list of known_renamers '$known_renamers' for command '$command'. Ensure remove_rename called from same context as rename_command was, or explicitly supply exact token or {<command> <renamer>}"
}
if {[dict exists $all_stacks $command]} {
set stack [dict get $all_stacks $command]
if {$tokenid ne ""} {
#token_or_command is a token as returned within the rename_command result dictionary
#search first dict value
set doomed_posn [lsearch -index 1 $stack $token_or_command]
} else {
#search second dict value
set matches [lsearch -all -index 3 $stack $renamer]
set doomed_posn [lindex $matches end] ;#we don't have a full token - pop last entry for this renamer
}
if {$doomed_posn ne "" && $doomed_posn > -1} {
set doomed_record [lindex $stack $doomed_posn]
if {[llength $stack] == ($doomed_posn + 1)} {
#last on stack - put the implemenation from the doomed_record back as the actual command
uplevel #0 [list rename $command ""]
uplevel #0 [list rename [dict get $doomed_record implementation] $command]
} elseif {[llength $stack] > ($doomed_posn + 1)} {
#there is at least one more record on the stack - rewrite it to point where the doomed_record pointed
set rewrite_posn [expr {$doomed_posn + 1}]
set rewrite_record [lindex $stack $rewrite_posn]
if {[dict get $rewrite_record next_implementor] ne $renamer} {
puts stderr "(commandstack::remove_rename) WARNING: next record on the commandstack didn't record '$renamer' as the next_implementor - not deleting implementation [dict get $rewrite_record implementation]"
} else {
uplevel #0 [list rename [dict get $rewrite_record implementation] ""]
}
dict set rewrite_record next_implementor [dict get $doomed_record next_implementor]
#don't update next_getter - it always refers to self
dict set rewrite_record implementation [dict get $doomed_record implementation]
lset stack $rewrite_posn $rewrite_record
dict set all_stacks $command $stack
}
set stack [lreplace $stack $doomed_posn $doomed_posn]
dict set all_stacks $command $stack
}
return $stack
}
return [list]
}
proc show_stack {{commandname_glob *}} {
variable all_stacks
if {![regexp {[?*]} $commandname_glob]} {
#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 "" && [package provide punk] ne ""} {
#punk pipeline also needed for patterns
return [punk::lib::pdict -channel none all_stacks $commandname_glob/@*/@*.@*]
} else {
set result ""
set matchedkeys [dict keys $all_stacks $commandname_glob]
#don't try to calculate widest on empty list
if {[llength $matchedkeys]} {
set widest [tcl::mathfunc::max {*}[lmap v $matchedkeys {tcl::string::length $v}]]
set indent [string repeat " " [expr {$widest + 3}]]
set indent2 "${indent} " ;#8 spaces for " i = " where i is 4 wide
set padkey [string repeat " " 20]
foreach k $matchedkeys {
append result "$k = "
set i 0
foreach stackmember [dict get $all_stacks $k] {
if {$i > 0} {
append result "\n$indent"
}
append result [string range "$i " 0 4] " = "
set j 0
dict for {k v} $stackmember {
if {$j > 0} {
append result "\n$indent2"
}
set displaykey [string range "$k$padkey" 0 20]
append result "$displaykey = $v"
incr j
}
incr i
}
append result \n
}
}
return $result
}
}
#review
#document when this is to be called. Wiping stacks without undoing renames seems odd.
proc Delete_stack {command} {
variable all_stacks
if {[dict exists $all_stacks $command]} {
dict unset all_stacks $command
return 1
} else {
return 1
}
}
#can be used to temporarily put a stack aside - should manually rename back when done.
#review - document how/when to use. example? intention?
proc Rename_stack {oldname newname} {
variable all_stacks
if {[dict exists $all_stacks $oldname]} {
if {[dict exists $all_stacks $newname]} {
error "(commandstack::rename_stack) cannot rename $oldname to $newname - $newname already exists in stack"
} else {
#set stackval [dict get $all_stacks $oldname]
#dict unset all_stacks $oldname
#dict set all_stacks $newname $stackval
dict set all_stacks $newname [lindex [list [dict get $all_stacks $oldname] [dict unset all_stacks $oldname]] 0]
}
}
}
}
namespace eval commandstack::lib {
proc splitx {str {regexp {[\t \r\n]+}}} {
#snarfed from tcllib textutil::splitx to avoid the dependency
# Bugfix 476988
if {[string length $str] == 0} {
return {}
}
if {[string length $regexp] == 0} {
return [::split $str ""]
}
if {[regexp $regexp {}]} {
return -code error "splitting on regexp \"$regexp\" would cause infinite loop"
}
set list {}
set start 0
while {[regexp -start $start -indices -- $regexp $str match submatch]} {
foreach {subStart subEnd} $submatch break
foreach {matchStart matchEnd} $match break
incr matchStart -1
incr matchEnd
lappend list [string range $str $start $matchStart]
if {$subStart >= $start} {
lappend list [string range $str $subStart $subEnd]
}
set start $matchEnd
}
lappend list [string range $str $start end]
return $list
}
proc split_body {procbody} {
set marker "#<commandstack_separator>#"
set header ""
set code ""
set found_marker 0
foreach ln [split $procbody \n] {
if {!$found_marker} {
if {[string trim $ln] eq $marker} {
set found_marker 1
} else {
append header $ln \n
}
} else {
append code $ln \n
}
}
if {$found_marker} {
return [list $header $code]
} else {
return [list "" $procbody]
}
}
}
package provide commandstack [namespace eval commandstack {
set version 0.3
}]

306
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/debug-1.0.6.tm vendored

@ -0,0 +1,306 @@
# Debug - a debug narrative logger.
# -- Colin McCormack / originally Wub server utilities
#
# Debugging areas of interest are represented by 'tokens' which have
# independantly settable levels of interest (an integer, higher is more detailed)
#
# Debug narrative is provided as a tcl script whose value is [subst]ed in the
# caller's scope if and only if the current level of interest matches or exceeds
# the Debug call's level of detail. This is useful, as one can place arbitrarily
# complex narrative in code without unnecessarily evaluating it.
#
# TODO: potentially different streams for different areas of interest.
# (currently only stderr is used. there is some complexity in efficient
# cross-threaded streams.)
# # ## ### ##### ######## ############# #####################
## Requisites
package require Tcl 8.5-
namespace eval ::debug {
namespace export -clear \
define on off prefix suffix header trailer \
names 2array level setting parray pdict \
nl tab hexl
namespace ensemble create -subcommands {}
}
# # ## ### ##### ######## ############# #####################
## API & Implementation
proc ::debug::noop {args} {}
proc ::debug::debug {tag message {level 1}} {
variable detail
if {$detail($tag) < $level} {
#puts stderr "$tag @@@ $detail($tag) >= $level"
return
}
variable prefix
variable suffix
variable header
variable trailer
variable fds
if {[info exists fds($tag)]} {
set fd $fds($tag)
} else {
set fd stderr
}
# Assemble the shown text from the user message and the various
# prefixes and suffices (global + per-tag).
set themessage ""
if {[info exists prefix(::)]} { append themessage $prefix(::) }
if {[info exists prefix($tag)]} { append themessage $prefix($tag) }
append themessage $message
if {[info exists suffix($tag)]} { append themessage $suffix($tag) }
if {[info exists suffix(::)]} { append themessage $suffix(::) }
# Resolve variables references and command invokations embedded
# into the message with plain text.
set code [catch {
set smessage [uplevel 1 [list ::subst -nobackslashes $themessage]]
set sheader [uplevel 1 [list ::subst -nobackslashes $header]]
set strailer [uplevel 1 [list ::subst -nobackslashes $trailer]]
} __ eo]
# And dump an internal error if that resolution failed.
if {$code} {
if {[catch {
set caller [info level -1]
}]} { set caller GLOBAL }
if {[string length $caller] >= 1000} {
set caller "[string range $caller 0 200]...[string range $caller end-200 end]"
}
foreach line [split $caller \n] {
puts -nonewline $fd "@@(DebugError from $tag ($eo): $line)"
}
return
}
# From here we have a good message to show. We only shorten it a
# bit if its a bit excessive in size.
if {[string length $smessage] > 4096} {
set head [string range $smessage 0 2048]
set tail [string range $smessage end-2048 end]
set smessage "${head}...(truncated)...$tail"
}
foreach line [split $smessage \n] {
puts $fd "$sheader$tag | $line$strailer"
}
return
}
# names - return names of debug tags
proc ::debug::names {} {
variable detail
return [lsort [array names detail]]
}
proc ::debug::2array {} {
variable detail
set result {}
foreach n [lsort [array names detail]] {
if {[interp alias {} debug.$n] ne "::debug::noop"} {
lappend result $n $detail($n)
} else {
lappend result $n -$detail($n)
}
}
return $result
}
# level - set level and fd for tag
proc ::debug::level {tag {level ""} {fd {}}} {
variable detail
# TODO: Force level >=0.
if {$level ne ""} {
set detail($tag) $level
}
if {![info exists detail($tag)]} {
set detail($tag) 1
}
variable fds
if {$fd ne {}} {
set fds($tag) $fd
}
return $detail($tag)
}
proc ::debug::header {text} { variable header $text }
proc ::debug::trailer {text} { variable trailer $text }
proc ::debug::define {tag} {
if {[interp alias {} debug.$tag] ne {}} return
off $tag
return
}
# Set a prefix/suffix to use for tag.
# The global (tag-independent) prefix/suffix is adressed through tag '::'.
# This works because colon (:) is an illegal character for user-specified tags.
proc ::debug::prefix {tag {theprefix {}}} {
variable prefix
set prefix($tag) $theprefix
if {[interp alias {} debug.$tag] ne {}} return
off $tag
return
}
proc ::debug::suffix {tag {theprefix {}}} {
variable suffix
set suffix($tag) $theprefix
if {[interp alias {} debug.$tag] ne {}} return
off $tag
return
}
# turn on debugging for tag
proc ::debug::on {tag {level ""} {fd {}}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} debug.$tag {} ::debug::debug $tag
return
}
# turn off debugging for tag
proc ::debug::off {tag {level ""} {fd {}}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} debug.$tag {} ::debug::noop
return
}
proc ::debug::setting {args} {
if {[llength $args] == 1} {
set args [lindex $args 0]
}
set fd stderr
if {[llength $args] % 2} {
set fd [lindex $args end]
set args [lrange $args 0 end-1]
}
foreach {tag level} $args {
if {$level > 0} {
level $tag $level $fd
interp alias {} debug.$tag {} ::debug::debug $tag
} else {
level $tag [expr {-$level}] $fd
interp alias {} debug.$tag {} ::debug::noop
}
}
return
}
# # ## ### ##### ######## ############# #####################
## Convenience commands.
# Format arrays and dicts as multi-line message.
# Insert newlines and tabs.
proc ::debug::nl {} { return \n }
proc ::debug::tab {} { return \t }
proc ::debug::parray {a {pattern *}} {
upvar 1 $a array
if {![array exists array]} {
error "\"$a\" isn't an array"
}
pdict [array get array] $pattern
}
proc ::debug::pdict {dict {pattern *}} {
set maxl 0
set names [lsort -dict [dict keys $dict $pattern]]
foreach name $names {
if {[string length $name] > $maxl} {
set maxl [string length $name]
}
}
set maxl [expr {$maxl + 2}]
set lines {}
foreach name $names {
set nameString [format (%s) $name]
lappend lines [format "%-*s = %s" \
$maxl $nameString \
[dict get $dict $name]]
}
return [join $lines \n]
}
proc ::debug::hexl {data {prefix {}}} {
set r {}
# Convert the data to hex and to characters.
binary scan $data H*@0a* hexa asciia
# Replace non-printing characters in the data with dots.
regsub -all -- {[^[:graph:] ]} $asciia {.} asciia
# Pad with spaces to a full multiple of 32/16.
set n [expr {[string length $hexa] % 32}]
if {$n < 32} { append hexa [string repeat { } [expr {32-$n}]] }
#puts "pad H [expr {32-$n}]"
set n [expr {[string length $asciia] % 32}]
if {$n < 16} { append asciia [string repeat { } [expr {16-$n}]] }
#puts "pad A [expr {32-$n}]"
# Reassemble formatted, in groups of 16 bytes/characters.
# The hex part is handled in groups of 32 nibbles.
set addr 0
while {[string length $hexa]} {
# Get front group of 16 bytes each.
set hex [string range $hexa 0 31]
set ascii [string range $asciia 0 15]
# Prep for next iteration
set hexa [string range $hexa 32 end]
set asciia [string range $asciia 16 end]
# Convert the hex to pairs of hex digits
regsub -all -- {..} $hex {& } hex
# Add the hex and latin-1 data to the result buffer
append r $prefix [format %04x $addr] { | } $hex { |} $ascii |\n
incr addr 16
}
# And done
return $r
}
# # ## ### ##### ######## ############# #####################
namespace eval debug {
variable detail ; # map: TAG -> level of interest
variable prefix ; # map: TAG -> message prefix to use
variable suffix ; # map: TAG -> message suffix to use
variable fds ; # map: TAG -> handle of open channel to log to.
variable header {} ; # per-line heading, subst'ed
variable trailer {} ; # per-line ending, subst'ed
# Notes:
# - The tag '::' is reserved. "prefix" and "suffix" use it to store
# the global message prefix / suffix.
# - prefix and suffix are applied per message.
# - header and trailer are per line. And should not generate multiple lines!
}
# # ## ### ##### ######## ############# #####################
## Ready
package provide debug 1.0.6
return

29
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fauxlink-0.1.0.tm → src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm vendored

@ -7,7 +7,7 @@
# (C) 2024 # (C) 2024
# #
# @@ Meta Begin # @@ Meta Begin
# Application fauxlink 0.1.0 # Application fauxlink 0.1.1
# Meta platform tcl # Meta platform tcl
# Meta license MIT # Meta license MIT
# @@ Meta End # @@ Meta End
@ -17,10 +17,10 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin fauxlink_module_fauxlink 0 0.1.0] #[manpage_begin fauxlink_module_fauxlink 0 0.1.1]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}] #[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}]
#[moddesc {fauxlink .fxlnk}] [comment {-- Description at end of page heading --}] #[moddesc {.fauxlink .fxlnk}] [comment {-- Description at end of page heading --}]
#[require fauxlink] #[require fauxlink]
#[keywords symlink faux fake shortcut toml] #[keywords symlink faux fake shortcut toml]
#[description] #[description]
@ -29,24 +29,25 @@
#[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as #[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as
#[para] archiving and packaging systems. #[para] archiving and packaging systems.
#[para] The target is specified in a minimally-encoded form in the filename itself - but still human readable. #[para] The target is specified in a minimally-encoded form in the filename itself - but still human readable.
#[para] format of name <nominalname>#<encodedtarget>.fxlnk #[para] format of name <nominalname>#<encodedtarget>.fauxlink
#[para] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget> #[para] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget>
#[para] The file extension must be .fauxlink or .fxlnk
#[para] The + symbol substitutes for forward-slashes. #[para] The + symbol substitutes for forward-slashes.
#[para] Other chars can be encoded using url-like encoding - (but only up to %7E !) #[para] Other chars can be encoded using url-like encoding - (but only up to %7E !)
#[para] We deliberately treat higher % sequences literally. #[para] We deliberately treat higher % sequences literally.
#[para] This means actual uri::urn encoded unicode sequences (e.g %E2%99%A5 [lb]heart[rb]) can remain literal for linking to urls. #[para] This means actual uri::urn encoded unicode sequences (e.g %E2%99%A5 [lb]heart[rb]) can remain literal for linking to urls.
#[para] e.g if an actual + or # is required in a filename or path segment they can be encoded as %2B & %23 #[para] e.g if an actual + or # is required in a filename or path segment they can be encoded as %2B & %23
#[para] e.g a link to a file file#A.txt in parent dir could be: #[para] e.g a link to a file file#A.txt in parent dir could be:
#[para] file%23A.txt#..+file%23A.txt.fxlnk #[para] file%23A.txt#..+file%23A.txt.fauxlink
#[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fxlnk #[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fauxlink
#[para] The <nominalname> can be unrelated to the actual target #[para] The <nominalname> can be unrelated to the actual target
#[para] e.g datafile.dat#..+file%23A.txt.fxlnk #[para] e.g datafile.dat#..+file%23A.txt.fauxlink
#[para] This system has no filesystem support - and must be completely application driven. #[para] This system has no filesystem support - and must be completely application driven.
#[para] This can be useful for example in application test packages which may be tarred or zipped and moved cross platform. #[para] This can be useful for example in application test packages which may be tarred or zipped and moved cross platform.
#[para] The target being fully specified in the name means the file doesn't have to be read for the target to be determined #[para] The target being fully specified in the name means the file doesn't have to be read for the target to be determined
#[para] Extensions to behaviour should be added in the file as text data in Toml format, #[para] Extensions to behaviour should be added in the file as text data in Toml format,
#[para] with custom data being under a single application-chosen table name #[para] with custom data being under a single application-chosen table name
#[para] The toplevel Toml table [fauxlink] is reserved for core extensions to this system. #[para] The toplevel Toml table [lb]fauxlink[rb] is reserved for core extensions to this system.
#[para] Aside from the 2 used for delimiting (+ #) #[para] Aside from the 2 used for delimiting (+ #)
#[para] certain characters which might normally be allowed in filesystems are required to be encoded #[para] certain characters which might normally be allowed in filesystems are required to be encoded
#[para] e.g space and tab are required to be %20 %09 #[para] e.g space and tab are required to be %20 %09
@ -63,9 +64,9 @@
#https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/url-encoding-unc-paths-not-url-decoded #https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/url-encoding-unc-paths-not-url-decoded
# ie "//server/c/Program files" works but "//server/c/Program%20Files" is now treated by windows as a literal path with %20 in it. # ie "//server/c/Program files" works but "//server/c/Program%20Files" is now treated by windows as a literal path with %20 in it.
#Using fauxlink - a link would be: #Using fauxlink - a link would be:
# "my-program-files#++server+c+Program%20Files.fxlnk" # "my-program-files#++server+c+Program%20Files.fauxlink"
#If we needed the old-style literal %20 it would become #If we needed the old-style literal %20 it would become
# "my-program-files#++server+c+Program%2520Files.fxlnk" # "my-program-files#++server+c+Program%2520Files.fauxlink"
# #
# The file:// scheme on windows supposedly *does* decode %xx (for use in a browser) # The file:// scheme on windows supposedly *does* decode %xx (for use in a browser)
# e.g # e.g
@ -296,12 +297,12 @@ namespace eval fauxlink {
set is_fauxlink 0 set is_fauxlink 0
#we'll process anyway - but return the result wrapped #we'll process anyway - but return the result wrapped
#This should allow deliberate erroring for the calling dict user if the extension difference is inadvertent #This should allow deliberate erroring for the calling dict user if the extension difference is inadvertent
#(e.g blindly processing all files in a folder that is normally only .fxlnk files - but then something added that happens #(e.g blindly processing all files in a folder that is normally only .fauxlink files - but then something added that happens
# to have # characters in it) # to have # characters in it)
#It also means if someone really wants to use the fauxlink semantics on a different file type #It also means if someone really wants to use the fauxlink semantics on a different file type
# - they can - but just have to access the results differently and take that (minor) risk. # - they can - but just have to access the results differently and take that (minor) risk.
#error "fauxlink::resolve refusing to process link $link - file extension must be .fxlnk or .fauxlink" #error "fauxlink::resolve refusing to process link $link - file extension must be .fxlnk or .fauxlink"
set err_extra "\nnonstandard extension '$extension_name' for fauxlink. Check that the call to fauxlink::resolve was deliberate" set err_extra "\nnonstandard extension '$extension_name' for fauxlink. (expected .fxlnk or .fauxlink) Check that the call to fauxlink::resolve was deliberate"
} else { } else {
set is_fauxlink 1 set is_fauxlink 1
set err_extra "" set err_extra ""
@ -318,7 +319,7 @@ namespace eval fauxlink {
#if there are 4 parts - the 3rd part is a tagset where each tag begins with @ #if there are 4 parts - the 3rd part is a tagset where each tag begins with @
#and each subsequent part is a comment. Empty comments are stripped from the comments list #and each subsequent part is a comment. Empty comments are stripped from the comments list
#A tagset can be empty - but if it's not empty it must contain at least one @ and must start with @ #A tagset can be empty - but if it's not empty it must contain at least one @ and must start with @
#e.g name.txt#path#@tag1@tag2#test###.fxlnk #e.g name.txt#path#@tag1@tag2#test###.fauxlink
#has a name, a target, 2 tags and one comment #has a name, a target, 2 tags and one comment
#check namespec already has required chars encoded #check namespec already has required chars encoded
@ -558,7 +559,7 @@ namespace eval fauxlink::system {
package provide fauxlink [namespace eval fauxlink { package provide fauxlink [namespace eval fauxlink {
variable pkg fauxlink variable pkg fauxlink
variable version variable version
set version 0.1.0 set version 0.1.1
}] }]
return return

74
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fileutil/paths-1.tm vendored

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

504
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm vendored

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

2714
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/flagfilter-0.3.tm vendored

File diff suppressed because it is too large Load Diff

325
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/funcl-0.1.tm vendored

@ -0,0 +1,325 @@
package provide funcl [namespace eval funcl {
variable version
set version 0.1
}]
#funcl = function list (nested call structure)
#
#a basic functional composition o combinator
#o(f,g)(x) == f(g(x))
namespace eval funcl {
#from punk::pipe
proc arg_is_script_shaped {arg} {
if {[string first " " $arg] >= 0} {
return 1
} elseif {[string first \n $arg] >= 0} {
return 1
} elseif {[string first ";" $arg] >= 0} {
return 1
} elseif {[string first \t $arg] >= 0} {
return 1
} else {
return 0
}
}
proc o args {
set closing [string repeat {]} [expr [llength $args]-1]]
set body "[join $args { [}] \$data $closing"
return $body
}
proc o_ args {
set body ""
set tails [lrepeat [llength $args] ""]
puts stdout "tails: $tails"
set end [lindex $args end]
if {[llength $end] == 1 && [arg_is_script_shaped $end]} {
set endfunc [string map "<end> $end" {uplevel 1 [list if 1 <end> ]}]
} else {
set endfunc $end
}
if {[llength $args] == 1} {
return $endfunc
}
set wrap { [}
append wrap $endfunc
append wrap { ]}
set i 0
foreach cmdlist [lrange $args 0 end-1] {
set is_script 0
if {([llength $cmdlist] == 1) && [arg_is_script_shaped [lindex $cmdlist 0]]} {
set is_script 1
set script [lindex $cmdlist 0]
}
set t ""
if {$i > 0} {
append body { [}
}
set posn [lsearch $cmdlist _]
if {$posn <= 0} {
append body $cmdlist
if {$i == ([llength $args]-2)} {
append body " $wrap"
}
#if {$i == [expr {[llength $args] -2}]} {
# #append body " \$data"
# append body " $wrap"
#}
if {$i > 0} {
set t {]}
}
} else {
append body [lrange $cmdlist 0 $posn-1]
if {$i == ([llength $args] -2)} {
#append body " \$data"
append body " $wrap"
}
set t [lrange $cmdlist $posn+1 end]
if {$i > 0} {
append t { ]}
}
}
lset tails $i $t
incr i
}
append body [join [lreverse $tails] " "]
puts stdout "tails: $tails"
return $body
}
#review - consider _call -- if count > 1 then they must all be callable cmdlists(?)
# what does it mean to have additional _fn wrapper with no other elements? (no actual function)
#e.g _fn 2 5 6 somefunc {_fn 1 3 {_call 1 3 xxx}} {_fn 1 4 command {_fn ...}}
# what type indicates running subtrees in parallel vs sequentially?
# any reason to have _call count other than 1? Presumably the parent node indicates the parallelism/sequentialism etc.
#
#
# accept or return a funcl (or funcltree if multiple funcls in one commandlist)
# also accept/return a call - return empty list if passed a call
proc next_funcl {funcl_or_tree} {
if {[lindex $funcl_or_tree 0] eq "_call"} {
return [list]
}
if {[lindex $funcl_or_tree 0] in [list "_fn" "_call"]} {
set funcl $funcl_or_tree
} else {
error "funcltree not implemented"
}
set count [lindex $funcl 1]
if {$count == 0} {
#null funcl.. what is it? metadata/placeholder?
return $funcl
}
set indices [lrange $funcl 2 [expr {1 + $count}]]
set i 0
foreach idx $indices {
if {$i > 0} {
#todo - return a funcltree
error "multi funcl not implemented"
}
set next [lindex $funcl $idx]
incr i
}
return $next
}
#convert a funcl to a tcl script
proc funcl_script {funcl} {
if {![llength $funcl]} {
return ""
}
set body ""
set tails [list]
set type [lindex $funcl 0]
if {$type ni [list "_fn" "_call"]} {
#todo - handle funcltree
error "type $type not implemented"
}
#only count of 1 with index 3 supported(?)
if {$type eq "_call"} {
#leaf
set cmdlist [lindex $funcl 3]
return $cmdlist
}
#we will use next_funcl to walk the nodes.. todo support treefuncl response from next_funcl which could branch multiple times.
#by continually passing back the resulting treefuncl/funcl to next_funcl we can process in correct order (?)
# we would still need to maintain state to stitch it back together once returned from a subtree..
# ie multiple tail parts
set count [lindex $funcl 1]
if {$count == 1} {
set idx [lindex $funcl 2]
if {$idx == 3} {
set cmdlist_pre [list]
} else {
set cmdlist_pre [lrange $funcl 3 $idx-1]
}
append body $cmdlist_pre
set t [lrange $funcl $idx+1 end]
lappend tails $t
} else {
#??
error "funcl_script branching not yet supported"
}
set get_next 1
set i 1
while {$get_next} {
set funcl [next_funcl $funcl]
if {![llength $funcl]} {
set get_next 0
}
lassign $funcl type count idx ;#todo support count > 1
if {$type eq "_call"} {
set get_next 0
}
set t ""
if {$type eq "_call"} {
append body { [}
append body [lindex $funcl $idx]
append body { ]}
} else {
append body { [}
if {$idx == 3} {
set cmdlist_pre [list]
} else {
set cmdlist_pre [lrange $funcl 3 $idx-1]
}
append body $cmdlist_pre
set t [lrange $funcl $idx+1 end]
lappend tails $t
lappend tails { ]}
}
incr i
}
append body [join [lreverse $tails] " "]
#puts stdout "tails: $tails"
return $body
}
interp alias "" o_of "" funcl::o_of_n 1
#o_of_n
#tcl list rep o combinator
#
# can take lists of ordinary commandlists, scripts and funcls
# _fn 1 x where 1 indicates number of subfuncls and where x indicates next funcl position (_fn list or _arg)
# _fn 0 indicates next item is an unwrapped commandlist (terminal command)
#
#o_of is equivalent to o_of_n 1 (1 argument o combinator)
#last n args are passed to the prior function
#e.g for n=1 f a b = f(a(b))
#e.g for n=2, e f a b = e(f(a b))
proc o_of_n {n args} {
puts stdout "o_of_n '$args'"
if {$n != 1} {
error "o_of_n only implemented for 1 sub-funcl"
}
set comp [list] ;#composition list
set end [lindex $args end]
if {[lindex $end 0] in {_fn _call}]} {
#is_funcl
set endfunc [lindex $args end]
} else {
if {[llength $end] == 1 && [arg_is_script_shaped $end]} {
#set endfunc [string map [list <end> $end] {uplevel 1 [list if 1 <end> ]}]
set endfunc [list _call 1 3 [list uplevel 1 [list if 1 [lindex $end 0]]]]
} else {
set endfunc [list _call 1 3 [list {*}$end]]
}
}
if {[llength $args] == 1} {
return $endfunc
}
set comp $endfunc
set revlist [lreverse [lrange $args 0 end-1]]
foreach cmdlist $revlist {
puts stderr "o_of_n >>-- $cmdlist"
if {([llength $cmdlist] == 1) && [arg_is_script_shaped [lindex $cmdlist 0]]} {
set is_script 1
set script [lindex $cmdlist 0]
set arglist [list data]
set comp [list _fn 1 6 call_script $script $arglist $comp]
} else {
set posn1 [expr {[llength $cmdlist] + 2 + $n}]
set comp [list _fn $n $posn1 {*}$cmdlist $comp]
}
}
return $comp
}
proc call_script {script argnames args} {
uplevel 3 [list if 1 [list apply [list $argnames $script] {*}$args]]
}
proc funcl_script_test {scr} {
do_funcl_script_test $scr
}
proc do_funcl_script_test {scr} {
#set j "in do_funcl_script_test"
#set data "xxx"
#puts '$scr'
if 1 $scr
}
#standard o_ with no script-handling
proc o_plain args {
set body ""
set i 0
set tails [lrepeat [llength $args] ""]
#puts stdout "tails: $tails"
foreach cmdlist $args {
set t ""
if {$i > 0} {
append body { [}
}
set posn [lsearch $cmdlist _]
if {$posn <= 0} {
append body $cmdlist
if {$i == ([llength $args] -1)} {
append body " \$data"
}
if {$i > 0} {
set t {]}
}
} else {
append body [lrange $cmdlist 0 $posn-1]
if {$i == ([llength $args] -1)} {
append body " \$data"
}
set t [lrange $cmdlist $posn+1 end]
if {$i > 0} {
append t { ]}
}
}
lset tails $i $t
incr i
}
append body [join [lreverse $tails] " "]
#puts stdout "tails: $tails"
return $body
}
#timings suggest no faster to split out the first item from the cmdlist loop
}

1297
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/logger-0.9.5.tm vendored

File diff suppressed because it is too large Load Diff

6411
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/metaface-1.2.5.tm vendored

File diff suppressed because it is too large Load Diff

705
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/modpod-0.1.0.tm vendored

@ -1,705 +0,0 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix 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.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application modpod 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin modpod_module_modpod 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 modpod]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of modpod
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by modpod
#[list_begin itemized]
package require Tcl 8.6-
package require struct::set ;#review
package require punk::lib
package require punk::args
#*** !doctools
#[item] [package {Tcl 8.6-}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval modpod::class {
#*** !doctools
#[subsection {Namespace modpod::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 modpod {
namespace export {[a-z]*}; # Convention: export all lowercase
variable connected
if {![info exists connected(to)]} {
set connected(to) list
}
variable modpodscript
set modpodscript [info script]
if {[string tolower [file extension $modpodscript]] eq ".tcl"} {
set connected(self) [file dirname $modpodscript]
} else {
#expecting a .tm
set connected(self) $modpodscript
}
variable loadables [info sharedlibextension]
variable sourceables {.tcl .tk} ;# .tm ?
#*** !doctools
#[subsection {Namespace modpod}]
#[para] Core API functions for modpod
#[list_begin definitions]
#proc sample1 {p1 args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [opt {?option value...?}]]
# #[para]Description of sample1
# return "ok"
#}
proc connect {args} {
puts stderr "modpod::connect--->>$args"
set argd [punk::args::get_dict {
-type -default ""
*values -min 1 -max 1
path -type string -minlen 1 -help "path to .tm file or toplevel .tcl script within #modpod-<pkg>-<ver> folder (unwrapped modpod)"
} $args]
catch {
punk::lib::showdict $argd ;#heavy dependencies
}
set opt_path [dict get $argd values path]
variable connected
set original_connectpath $opt_path
set modpodpath [modpod::system::normalize $opt_path] ;#
if {$modpodpath in $connected(to)} {
return [dict create ok ALREADY_CONNECTED]
}
lappend connected(to) $modpodpath
set connected(connectpath,$opt_path) $original_connectpath
set is_sourced [expr {[file normalize $modpodpath] eq [file normalize [info_script]]}]
set connected(location,$modpodpath) [file dirname $modpodpath]
set connected(startdata,$modpodpath) -1
set connected(type,$modpodpath) [dict get $argd-opts -type]
set connected(fh,$modpodpath) ""
if {[string range [file tail $modpodpath] 0 7] eq "#modpod-"} {
set connected(type,$modpodpath) "unwrapped"
lassign [::split [file tail [file dirname $modpodpath]] -] connected(package,$modpodpath) connected(version,$modpodpath)
set this_pkg_tm_folder [file dirname [file dirname $modpodpath]]
} else {
#connect to .tm but may still be unwrapped version available
lassign [::split [file rootname [file tail $modpodath]] -] connected(package,$modpodpath) connected(version,$modpodpath)
set this_pkg_tm_folder [file dirname $modpodpath]
if {$connected(type,$modpodpath) ne "unwrapped"} {
#Not directly connected to unwrapped version - but may still be redirected there
set unwrappedFolder [file join $connected(location,$modpodpath) #modpod-$connected(package,$modpodpath)-$connected(version,$modpodpath)]
if {[file exists $unwrappedFolder]} {
#folder with exact version-match must exist for redirect to 'unwrapped'
set con(type,$modpodpath) "modpod-redirecting"
}
}
}
set unwrapped_tm_file [file join $this_pkg_tm_folder] "[set connected(package,$modpodpath)]-[set connected(version,$modpodpath)].tm"
set connected(tmfile,$modpodpath)
set tail_segments [list]
set lcase_tmfile_segments [string tolower [file split $this_pkg_tm_folder]]
set lcase_modulepaths [string tolower [tcl::tm::list]]
foreach lc_mpath $lcase_modulepaths {
set mpath_segments [file split $lc_mpath]
if {[llength [struct::set intersect $lcase_tmfile_segments $mpath_segments]] == [llength $mpath_segments]} {
set tail_segments [lrange [file split $this_pkg_tm_folder] [llength $mpath_segments] end]
break
}
}
if {[llength $tail_segments]} {
set connected(fullpackage,$modpodpath) [join [concat $tail_segments [set connected(package,$modpodpath)]] ::] ;#full name of package as used in package require
} else {
set connected(fullpackage,$modpodpath) [set connected(package,$modpodpath)]
}
switch -exact -- $connected(type,$modpodpath) {
"modpod-redirecting" {
#redirect to the unwrapped version
set loadscript_name [file join $unwrappedFolder #modpod-loadscript-$con(package,$modpod).tcl]
}
"unwrapped" {
if {[info commands ::thread::id] ne ""} {
set from [pid],[thread::id]
} else {
set from [pid]
}
#::modpod::Puts stderr "$from-> Package $connected(package,$modpodpath)-$connected(version,$modpodpath) is using unwrapped version: $modpodpath"
return [list ok ""]
}
default {
#autodetect .tm - zip/tar ?
#todo - use vfs ?
#connect to tarball - start at 1st header
set connected(startdata,$modpodpath) 0
set fh [open $modpodpath r]
set connected(fh,$modpodpath) $fh
fconfigure $fh -encoding iso8859-1 -translation binary -eofchar {}
if {$connected(startdata,$modpodpath) >= 0} {
#verify we have a valid tar header
if {![catch {::modpod::system::tar::readHeader [red $fh 512]}]} {
seek $fh $connected(startdata,$modpodpath) start
return [list ok $fh]
} else {
#error "cannot verify tar header"
}
}
lpop connected(to) end
set connected(startdata,$modpodpath) -1
unset connected(fh,$modpodpath)
catch {close $fh}
return [dict create err {Does not appear to be a valid modpod}]
}
}
}
proc disconnect {{modpod ""}} {
variable connected
if {![llength $connected(to)]} {
return 0
}
if {$modpod eq ""} {
puts stderr "modpod::disconnect WARNING: modpod not explicitly specified. Disconnecting last connected: [lindex $connected(to) end]"
set modpod [lindex $connected(to) end]
}
if {[set posn [lsearch $connected(to) $modpod]] == -1} {
puts stderr "modpod::disconnect WARNING: disconnect called when not connected: $modpod"
return 0
}
if {[string length $connected(fh,$modpod)]} {
close $connected(fh,$modpod)
}
array unset connected *,$modpod
set connected(to) [lreplace $connected(to) $posn $posn]
return 1
}
proc get {args} {
set argd [punk::args::get_dict {
-from -default "" -help "path to pod"
*values -min 1 -max 1
filename
} $args]
set frompod [dict get $argd opts -from]
set filename [dict get $argd values filename]
variable connected
set modpod [::tarjar::system::connect_if_not $frompod]
set fh $connected(fh,$modpod)
if {$connected(type,$modpod) eq "unwrapped"} {
#for unwrapped connection - $connected(location) already points to the #modpod-pkg-ver folder
if {[string range $filename 0 0 eq "/"]} {
#absolute path (?)
set path [file join $connected(location,$modpod) .. [string trim $filename /]]
} else {
#relative path - use #modpod-xxx as base
set path [file join $connected(location,$modpod) $filename]
}
set fd [open $path r]
#utf-8?
#fconfigure $fd -encoding iso8859-1 -translation binary
return [list ok [lindex [list [read $fd] [close $fd]] 0]]
} else {
#read from vfs
puts stderr "get $filename from wrapped pod '$frompod' not implemented"
}
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace modpod ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval modpod::lib {
namespace export {[a-z]*}; # Convention: export all lowercase
namespace path [namespace parent]
#*** !doctools
#[subsection {Namespace modpod::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
proc is_valid_tm_version {versionpart} {
#Needs to be suitable for use with Tcl's 'package vcompare'
if {![catch [list package vcompare $versionparts $versionparts]]} {
return 1
} else {
return 0
}
}
proc make_zip_modpod {zipfile outfile} {
set mount_stub {
#zip file with Tcl loader prepended.
#generated using modpod::make_zip_modpod
if {[catch {file normalize [info script]} modfile]} {
error "modpod zip stub error. Unable to determine module path. (possible safe interp restrictions?)"
}
if {$modfile eq "" || ![file exists $modfile]} {
error "modpod zip stub error. Unable to determine module path"
}
set moddir [file dirname $modfile]
set mod_and_ver [file rootname [file tail $modfile]]
lassign [split $mod_and_ver -] moduletail version
if {[file exists $moddir/#modpod-$mod_and_ver]} {
source $moddir/#modpod-$mod_and_ver/$mod_and_ver.tm
} else {
#determine module namespace so we can mount appropriately
proc intersect {A B} {
if {[llength $A] == 0} {return {}}
if {[llength $B] == 0} {return {}}
if {[llength $B] > [llength $A]} {
set res $A
set A $B
set B $res
}
set res {}
foreach x $A {set ($x) {}}
foreach x $B {
if {[info exists ($x)]} {
lappend res $x
}
}
return $res
}
set lcase_tmfile_segments [string tolower [file split $moddir]]
set lcase_modulepaths [string tolower [tcl::tm::list]]
foreach lc_mpath $lcase_modulepaths {
set mpath_segments [file split $lc_mpath]
if {[llength [intersect $lcase_tmfile_segments $mpath_segments]] == [llength $mpath_segments]} {
set tail_segments [lrange [file split $moddir] [llength $mpath_segments] end] ;#use propertly cased tail
break
}
}
if {[llength $tail_segments]} {
set fullpackage [join [concat $tail_segments $moduletail] ::] ;#full name of package as used in package require
set mount_at #modpod/[file join {*}$tail_segments]/#mounted-modpod-$mod_and_ver
} else {
set fullpackage $moduletail
set mount_at #modpod/#mounted-modpod-$mod_and_ver
}
if {[info commands tcl::zipfs::mount] ne ""} {
#argument order changed to be consistent with vfs::zip::Mount etc
#early versions: zipfs::Mount mountpoint zipname
#since 2023-09: zipfs::Mount zipname mountpoint
#don't use 'file exists' when testing mountpoints. (some versions at least give massive delays on windows platform for non-existance)
#This is presumably related to // being interpreted as a network path
set mountpoints [dict keys [tcl::zipfs::mount]]
if {"//zipfs:/$mount_at" ni $mountpoints} {
#despite API change tcl::zipfs package version was unfortunately not updated - so we don't know argument order without trying it
if {[catch {
#tcl::zipfs::mount $modfile //zipfs:/#mounted-modpod-$mod_and_ver ;#extremely slow if this is a wrong guess (artifact of aforementioned file exists issue ?)
#puts "tcl::zipfs::mount $modfile $mount_at"
tcl::zipfs::mount $modfile $mount_at
} errM]} {
#try old api
if {![catch {tcl::zipfs::mount //zipfs:/$mount_at $modfile}]} {
puts stderr "modpod stub>>> tcl::zipfs::mount <file> <mountpoint> failed.\nbut old api: tcl::zipfs::mount <mountpoint> <file> succeeded\n tcl::zipfs::mount //zipfs://$mount_at $modfile"
puts stderr "Consider upgrading tcl runtime to one with fixed zipfs API"
}
}
if {![file exists //zipfs:/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm]} {
puts stderr "modpod stub>>> mount at //zipfs:/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm failed\n zipfs mounts: [zipfs mount]"
#tcl::zipfs::unmount //zipfs:/$mount_at
error "Unable to find $mod_and_ver.tm in $modfile for module $fullpackage"
}
}
# #modpod-$mod_and_ver subdirectory always present in the archive so it can be conveniently extracted and run in that form
source //zipfs:/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm
} else {
#fallback to slower vfs::zip
#NB. We don't create the intermediate dirs - but the mount still works
if {![file exists $moddir/$mount_at]} {
if {[catch {package require vfs::zip} errM]} {
set msg "Unable to load vfs::zip package to mount module $mod_and_ver"
append msg \n "If vfs::zip is unavailable - the module can still be loaded by manually unzipping the file $modfile in place."
append msg \n "The unzipped data will all be contained in a folder named #modpod-$mod_and_ver in the same parent folder as $modfile"
error $msg
} else {
set fd [vfs::zip::Mount $modfile $moddir/$mount_at]
if {![file exists $moddir/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm]} {
vfs::zip::Unmount $fd $moddir/$mount_at
error "Unable to find $mod_and_ver.tm in $modfile for module $fullpackage"
}
}
}
source $moddir/$mount_at/#modpod-$mod_and_ver/$mod_and_ver.tm
}
}
#zipped data follows
}
#todo - test if zipfile has #modpod-loadcript.tcl before even creating
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub
}
proc make_zip_modpod1 {zipfile outfile} {
set mount_stub {
#zip file with Tcl loader prepended.
#generated using modpod::make_zip_modpod
if {[catch {file normalize [info script]} modfile]} {
error "modpod zip stub error. Unable to determine module path. (possible safe interp restrictions?)"
}
if {$modfile eq "" || ![file exists $modfile]} {
error "modpod zip stub error. Unable to determine module path"
}
set moddir [file dirname $modfile]
set mod_and_ver [file rootname [file tail $modfile]]
lassign [split $mod_and_ver -] moduletail version
if {[file exists $moddir/#modpod-$mod_and_ver]} {
source $moddir/#modpod-$mod_and_ver/$mod_and_ver.tm
} else {
if {![file exists $moddir/#mounted-modpod-$mod_and_ver]} {
if {[catch {package require vfs::zip} errM]} {
set msg "Unable to load vfs::zip package to mount module $mod_and_ver"
append msg \n "If vfs::zip is unavailable - the module can still be loaded by manually unzipping the file $modfile in place."
append msg \n "The unzipped data will all be contained in a folder named #modpod-$mod_and_ver in the same parent folder as $
}
set fd [vfs::zip::Mount $modfile $moddir/#mounted-modpod-$mod_and_ver]
if {![file exists $moddir/#mounted-modpod-$mod_and_ver/#modpod-$mod_and_ver/$mod_and_ver.tm]} {
vfs::zip::Unmount $fd $moddir/#mounted-modpod-$mod_and_ver
error "Unable to find #modpod-$mod_and_ver/$mod_and_ver.tm in $modfile"
}
}
source $moddir/#mounted-modpod-$mod_and_ver/#modpod-$mod_and_ver/$mod_and_ver.tm
}
#zipped data follows
}
#todo - test if zipfile has #modpod-loadcript.tcl before even creating
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub
}
proc make_zip_source_mountable {zipfile outfile} {
set mount_stub {
package require vfs::zip
vfs::zip::Mount [info script] [info script]
}
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace modpod::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
namespace eval modpod::system {
#*** !doctools
#[subsection {Namespace modpod::system}]
#[para] Internal functions that are not part of the API
#deflate,store only supported
proc make_mountable_zip {zipfile outfile mount_stub} {
set in [open $zipfile r]
fconfigure $in -encoding iso8859-1 -translation binary
set out [open $outfile w+]
fconfigure $out -encoding iso8859-1 -translation binary
puts -nonewline $out $mount_stub
set offset [tell $out]
lappend report "sfx stub size: $offset"
fcopy $in $out
close $in
set size [tell $out]
#Now seek in $out to find the end of directory signature:
#The structure itself is 24 bytes Long, followed by a maximum of 64Kbytes text
if {$size < 65559} {
set seek 0
} else {
set seek [expr {$size - 65559}]
}
seek $out $seek
set data [read $out]
set start_of_end [string last "\x50\x4b\x05\x06" $data]
#set start_of_end [expr {$start_of_end + $seek}]
incr start_of_end $seek
lappend report "START-OF-END: $start_of_end ([expr {$start_of_end - $size}]) [string length $data]"
seek $out $start_of_end
set end_of_ctrl_dir [read $out]
binary scan $end_of_ctrl_dir issssiis eocd(signature) eocd(disknbr) eocd(ctrldirdisk) \
eocd(numondisk) eocd(totalnum) eocd(dirsize) eocd(diroffset) eocd(comment_len)
lappend report "End of central directory: [array get eocd]"
seek $out [expr {$start_of_end+16}]
#adjust offset of start of central directory by the length of our sfx stub
puts -nonewline $out [binary format i [expr {$eocd(diroffset) + $offset}]]
flush $out
seek $out $start_of_end
set end_of_ctrl_dir [read $out]
binary scan $end_of_ctrl_dir issssiis eocd(signature) eocd(disknbr) eocd(ctrldirdisk) \
eocd(numondisk) eocd(totalnum) eocd(dirsize) eocd(diroffset) eocd(comment_len)
# 0x06054b50 - end of central dir signature
puts stderr "$end_of_ctrl_dir"
puts stderr "comment_len: $eocd(comment_len)"
puts stderr "eocd sig: $eocd(signature) [punk::lib::dec2hex $eocd(signature)]"
lappend report "New dir offset: $eocd(diroffset)"
lappend report "Adjusting $eocd(totalnum) zip file items."
catch {
punk::lib::showdict -roottype list -chan stderr $report ;#heavy dependencies
}
seek $out $eocd(diroffset)
for {set i 0} {$i <$eocd(totalnum)} {incr i} {
set current_file [tell $out]
set fileheader [read $out 46]
puts --------------
puts [ansistring VIEW -lf 1 $fileheader]
puts --------------
#binary scan $fileheader is2sss2ii2s3ssii x(sig) x(version) x(flags) x(method) \
# x(date) x(crc32) x(sizes) x(lengths) x(diskno) x(iattr) x(eattr) x(offset)
binary scan $fileheader ic4sss2ii2s3ssii x(sig) x(version) x(flags) x(method) \
x(date) x(crc32) x(sizes) x(lengths) x(diskno) x(iattr) x(eattr) x(offset)
set ::last_header $fileheader
puts "sig: $x(sig) (hex: [punk::lib::dec2hex $x(sig)])"
puts "ver: $x(version)"
puts "method: $x(method)"
#33639248 dec = 0x02014b50 - central file header signature
if { $x(sig) != 33639248 } {
error "modpod::system::make_mountable_zip Bad file header signature at item $i: dec:$x(sig) hex:[punk::lib::dec2hex $x(sig)]"
}
foreach size $x(lengths) var {filename extrafield comment} {
if { $size > 0 } {
set x($var) [read $out $size]
} else {
set x($var) ""
}
}
set next_file [tell $out]
lappend report "file $i: $x(offset) $x(sizes) $x(filename)"
seek $out [expr {$current_file+42}]
puts -nonewline $out [binary format i [expr {$x(offset)+$offset}]]
#verify:
flush $out
seek $out $current_file
set fileheader [read $out 46]
lappend report "old $x(offset) + $offset"
binary scan $fileheader is2sss2ii2s3ssii x(sig) x(version) x(flags) x(method) \
x(date) x(crc32) x(sizes) x(lengths) x(diskno) x(iattr) x(eattr) x(offset)
lappend report "new $x(offset)"
seek $out $next_file
}
close $out
#pdict/showdict reuire punk & textlib - ie lots of dependencies
#don't fall over just because of that
catch {
punk::lib::showdict -roottype list -chan stderr $report
}
#puts [join $report \n]
return
}
proc connect_if_not {{podpath ""}} {
upvar ::modpod::connected connected
set podpath [::modpod::system::normalize $podpath]
set docon 0
if {![llength $connected(to)]} {
if {![string length $podpath]} {
error "modpod::system::connect_if_not - Not connected to a modpod file, and no podpath specified"
} else {
set docon 1
}
} else {
if {![string length $podpath]} {
set podpath [lindex $connected(to) end]
puts stderr "modpod::system::connect_if_not WARNING: using last connected modpod:$podpath for operation\n -podpath not explicitly specified during operation: [info level -1]"
} else {
if {$podpath ni $connected(to)} {
set docon 1
}
}
}
if {$docon} {
if {[lindex [modpod::connect $podpath]] 0] ne "ok"} {
error "modpod::system::connect_if_not error. file $podpath does not seem to be a valid modpod"
} else {
return $podpath
}
}
#we were already connected
return $podpath
}
proc myversion {} {
upvar ::modpod::connected connected
set script [info script]
if {![string length $script]} {
error "No result from \[info script\] - modpod::system::myversion should only be called from within a loading modpod"
}
set fname [file tail [file rootname [file normalize $script]]]
set scriptdir [file dirname $script]
if {![string match "#modpod-*" $fname]} {
lassign [lrange [split $fname -] end-1 end] _pkgname version
} else {
lassign [scan [file tail [file rootname $script]] {#modpod-loadscript-%[a-z]-%s}] _pkgname version
if {![string length $version]} {
#try again on the name of the containing folder
lassign [scan [file tail $scriptdir] {#modpod-%[a-z]-%s}] _pkgname version
#todo - proper walk up the directory tree
if {![string length $version]} {
#try again on the grandparent folder (this is a standard depth for sourced .tcl files in a modpod)
lassign [scan [file tail [file dirname $scriptdir]] {#modpod-%[a-z]-%s}] _pkgname version
}
}
}
#tarjar::Log debug "'myversion' determined version for [info script]: $version"
return $version
}
proc myname {} {
upvar ::modpod::connected connected
set script [info script]
if {![string length $script]} {
error "No result from \[info script\] - modpod::system::myname should only be called from within a loading modpod"
}
return $connected(fullpackage,$script)
}
proc myfullname {} {
upvar ::modpod::connected connected
set script [info script]
#set script [::tarjar::normalize $script]
set script [file normalize $script]
if {![string length $script]} {
error "No result from \[info script\] - modpod::system::myfullname should only be called from within a loading tarjar"
}
return $::tarjar::connected(fullpackage,$script)
}
proc normalize {path} {
#newer versions of Tcl don't do tilde sub
#Tcl's 'file normalize' seems to do some unfortunate tilde substitution on windows.. (at least for relative paths)
# we take the assumption here that if Tcl's tilde substitution is required - it should be done before the path is provided to this function.
set matilda "<_tarjar_tilde_placeholder_>" ;#token that is *unlikely* to occur in the wild, and is somewhat self describing in case it somehow ..escapes..
set path [string map [list ~ $matilda] $path] ;#give our tildes to matilda to look after
set path [file normalize $path]
#set path [string tolower $path] ;#must do this after file normalize
return [string map [list $matilda ~] $path] ;#get our tildes back.
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide modpod [namespace eval modpod {
variable pkg modpod
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

37
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/modpod-0.1.1.tm → src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/modpod-0.1.2.tm vendored

@ -7,7 +7,7 @@
# (C) 2024 # (C) 2024
# #
# @@ Meta Begin # @@ Meta Begin
# Application modpod 0.1.1 # Application modpod 0.1.2
# Meta platform tcl # Meta platform tcl
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
@ -17,7 +17,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin modpod_module_modpod 0 0.1.1] #[manpage_begin modpod_module_modpod 0 0.1.2]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] #[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}] #[moddesc {-}] [comment {-- Description at end of page heading --}]
@ -135,9 +135,10 @@ namespace eval modpod {
proc connect {args} { proc connect {args} {
puts stderr "modpod::connect--->>$args" puts stderr "modpod::connect--->>$args"
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
@id -id ::modpod::connect
-type -default "" -type -default ""
*values -min 1 -max 1 @values -min 1 -max 1
path -type string -minlen 1 -help "path to .tm file or toplevel .tcl script within #modpod-<pkg>-<ver> folder (unwrapped modpod)" path -type string -minsize 1 -help "path to .tm file or toplevel .tcl script within #modpod-<pkg>-<ver> folder (unwrapped modpod)"
} $args] } $args]
catch { catch {
punk::lib::showdict $argd ;#heavy dependencies punk::lib::showdict $argd ;#heavy dependencies
@ -329,14 +330,16 @@ namespace eval modpod::lib {
#zipfile is a pure zip at this point - ie no script/exe header #zipfile is a pure zip at this point - ie no script/exe header
proc make_zip_modpod {args} { proc make_zip_modpod {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
-offsettype -default "file" -choices {archive file} -help "Whether zip offsets are relative to start of file or start of zip-data within the file. @id -id ::modpod::lib::make_zip_modpod
'archive' relative offsets are easier to work with (for writing/updating) in tools such as 7zip,peazip, -offsettype -default "archive" -choices {archive file} -help\
but other tools may be easier with 'file' relative offsets. (e.g info-zip,pkzip) "Whether zip offsets are relative to start of file or start of zip-data within the file.
info-zip's 'zip -A' can sometimes convert archive-relative to file-relative. 'archive' relative offsets are easier to work with (for writing/updating) in tools such as 7zip,peazip,
-offsettype archive is equivalent to plain 'cat prefixfile zipfile > modulefile'" but other tools may be easier with 'file' relative offsets. (e.g info-zip,pkzip)
*values -min 2 -max 2 info-zip's 'zip -A' can sometimes convert archive-relative to file-relative.
zipfile -type path -minlen 1 -help "path to plain zip file with subfolder #modpod-packagename-version containing .tm, data files and/or binaries" -offsettype archive is equivalent to plain 'cat prefixfile zipfile > modulefile'"
outfile -type path -minlen 1 -help "path to output file. Name should be of the form packagename-version.tm" @values -min 2 -max 2
zipfile -type path -minsize 1 -help "path to plain zip file with subfolder #modpod-packagename-version containing .tm, data files and/or binaries"
outfile -type path -minsize 1 -help "path to output file. Name should be of the form packagename-version.tm"
} $args] } $args]
set zipfile [dict get $argd values zipfile] set zipfile [dict get $argd values zipfile]
set outfile [dict get $argd values outfile] set outfile [dict get $argd values outfile]
@ -468,14 +471,14 @@ namespace eval modpod::system {
#deflate,store only supported #deflate,store only supported
#zipfile here is plain zip - no script/exe prefix part. #zipfile here is plain zip - no script/exe prefix part.
proc make_mountable_zip {zipfile outfile mount_stub {offsettype "file"}} { proc make_mountable_zip {zipfile outfile mount_stub {offsettype "archive"}} {
set inzip [open $zipfile r] set inzip [open $zipfile r]
fconfigure $inzip -encoding iso8859-1 -translation binary fconfigure $inzip -encoding iso8859-1 -translation binary
set out [open $outfile w+] set out [open $outfile w+]
fconfigure $out -encoding iso8859-1 -translation binary fconfigure $out -encoding iso8859-1 -translation binary
puts -nonewline $out $mount_stub puts -nonewline $out $mount_stub
set stuboffset [tell $out] set stuboffset [tell $out]
lappend report "sfx stub size: $stuboffset" lappend report "stub size: $stuboffset"
fcopy $inzip $out fcopy $inzip $out
close $inzip close $inzip
@ -486,7 +489,9 @@ namespace eval modpod::system {
if {$offsettype eq "file"} { if {$offsettype eq "file"} {
#make zip offsets relative to start of whole file including prepended script. #make zip offsets relative to start of whole file including prepended script.
#(same offset structure as Tcl's 'zipfs mkimg' as at 2024-10) #same offset structure as Tcl's 'zipfs mkimg' as at 2024-10
#not editable by 7z,nanazip,peazip
#we aren't adding any new files/folders so we can edit the offsets in place #we aren't adding any new files/folders so we can edit the offsets in place
#Now seek in $out to find the end of directory signature: #Now seek in $out to find the end of directory signature:
@ -688,7 +693,7 @@ namespace eval modpod::system {
package provide modpod [namespace eval modpod { package provide modpod [namespace eval modpod {
variable pkg modpod variable pkg modpod
variable version variable version
set version 0.1.1 set version 0.1.2
}] }]
return return

33
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm vendored

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

2711
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/overtype-1.6.5.tm vendored

File diff suppressed because it is too large Load Diff

1285
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/pattern-1.2.4.tm vendored

File diff suppressed because it is too large Load Diff

645
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm vendored

@ -0,0 +1,645 @@
package provide patterncmd [namespace eval patterncmd {
variable version
set version 1.2.4
}]
namespace eval pattern {
variable idCounter 1 ;#used by pattern::uniqueKey
namespace eval cmd {
namespace eval util {
package require overtype
variable colwidths_lib [dict create]
variable colwidths_lib_default 15
dict set colwidths_lib "library" [list ch " " num 21 head "|" tail ""]
dict set colwidths_lib "version" [list ch " " num 7 head "|" tail ""]
dict set colwidths_lib "type" [list ch " " num 9 head "|" tail ""]
dict set colwidths_lib "note" [list ch " " num 31 head "|" tail "|"]
proc colhead {type args} {
upvar #0 ::pattern::cmd::util::colwidths_$type colwidths
set line ""
foreach colname [dict keys $colwidths] {
append line "[col $type $colname [string totitle $colname] {*}$args]"
}
return $line
}
proc colbreak {type} {
upvar #0 ::pattern::cmd::util::colwidths_$type colwidths
set line ""
foreach colname [dict keys $colwidths] {
append line "[col $type $colname {} -backchar - -headoverridechar + -tailoverridechar +]"
}
return $line
}
proc col {type col val args} {
# args -head bool -tail bool ?
#----------------------------------------------------------------------------
set known_opts [list -backchar -headchar -tailchar -headoverridechar -tailoverridechar -justify]
dict set default -backchar ""
dict set default -headchar ""
dict set default -tailchar ""
dict set default -headoverridechar ""
dict set default -tailoverridechar ""
dict set default -justify "left"
if {([llength $args] % 2) != 0} {
error "(pattern::cmd::util::col) ERROR: uneven options supplied - must be of form '-option value' "
}
foreach {k v} $args {
if {$k ni $known_opts} {
error "((pattern::cmd::util::col) ERROR: option '$k' not in known options: '$known_opts'"
}
}
set opts [dict merge $default $args]
set backchar [dict get $opts -backchar]
set headchar [dict get $opts -headchar]
set tailchar [dict get $opts -tailchar]
set headoverridechar [dict get $opts -headoverridechar]
set tailoverridechar [dict get $opts -tailoverridechar]
set justify [dict get $opts -justify]
#----------------------------------------------------------------------------
upvar #0 ::pattern::cmd::util::colwidths_$type colwidths
#calculate headwidths
set headwidth 0
set tailwidth 0
foreach {key def} $colwidths {
set thisheadlen [string length [dict get $def head]]
if {$thisheadlen > $headwidth} {
set headwidth $thisheadlen
}
set thistaillen [string length [dict get $def tail]]
if {$thistaillen > $tailwidth} {
set tailwidth $thistaillen
}
}
set spec [dict get $colwidths $col]
if {[string length $backchar]} {
set ch $backchar
} else {
set ch [dict get $spec ch]
}
set num [dict get $spec num]
set headchar [dict get $spec head]
set tailchar [dict get $spec tail]
if {[string length $headchar]} {
set headchar $headchar
}
if {[string length $tailchar]} {
set tailchar $tailchar
}
#overrides only apply if the head/tail has a length
if {[string length $headchar]} {
if {[string length $headoverridechar]} {
set headchar $headoverridechar
}
}
if {[string length $tailchar]} {
if {[string length $tailoverridechar]} {
set tailchar $tailoverridechar
}
}
set head [string repeat $headchar $headwidth]
set tail [string repeat $tailchar $tailwidth]
set base [string repeat $ch [expr {$headwidth + $num + $tailwidth}]]
if {$justify eq "left"} {
set left_done [overtype::left $base "$head$val"]
return [overtype::right $left_done "$tail"]
} elseif {$justify in {centre center}} {
set mid_done [overtype::centre $base $val]
set left_mid_done [overtype::left $mid_done $head]
return [overtype::right $left_mid_done $tail]
} else {
set right_done [overtype::right $base "$val$tail"]
return [overtype::left $right_done $head]
}
}
}
}
}
#package require pattern
proc ::pattern::libs {} {
set libs [list \
pattern {-type core -note "alternative:pattern2"}\
pattern2 {-type core -note "alternative:pattern"}\
patterncmd {-type core}\
metaface {-type core}\
patternpredator2 {-type core}\
patterndispatcher {-type core}\
patternlib {-type core}\
patterncipher {-type optional -note optional}\
]
package require overtype
set result ""
append result "[cmd::util::colbreak lib]\n"
append result "[cmd::util::colhead lib -justify centre]\n"
append result "[cmd::util::colbreak lib]\n"
foreach libname [dict keys $libs] {
set libinfo [dict get $libs $libname]
append result [cmd::util::col lib library $libname]
if {[catch [list package present $libname] ver]} {
append result [cmd::util::col lib version "N/A"]
} else {
append result [cmd::util::col lib version $ver]
}
append result [cmd::util::col lib type [dict get $libinfo -type]]
if {[dict exists $libinfo -note]} {
set note [dict get $libinfo -note]
} else {
set note ""
}
append result [cmd::util::col lib note $note]
append result "\n"
}
append result "[cmd::util::colbreak lib]\n"
return $result
}
proc ::pattern::record {recname fields} {
if {[uplevel 1 [list namespace which $recname]] ne ""} {
error "(pattern::record) Can't create command '$recname': A command of that name already exists"
}
set index -1
set accessor [list ::apply {
{index rec args}
{
if {[llength $args] == 0} {
return [lindex $rec $index]
}
if {[llength $args] == 1} {
return [lreplace $rec $index $index [lindex $args 0]]
}
error "Invalid number of arguments."
}
}]
set map {}
foreach field $fields {
dict set map $field [linsert $accessor end [incr index]]
}
uplevel 1 [list namespace ensemble create -command $recname -map $map -parameters rec]
}
proc ::pattern::record2 {recname fields} {
if {[uplevel 1 [list namespace which $recname]] ne ""} {
error "(pattern::record) Can't create command '$recname': A command of that name already exists"
}
set index -1
set accessor [list ::apply]
set template {
{rec args}
{
if {[llength $args] == 0} {
return [lindex $rec %idx%]
}
if {[llength $args] == 1} {
return [lreplace $rec %idx% %idx% [lindex $args 0]]
}
error "Invalid number of arguments."
}
}
set map {}
foreach field $fields {
set body [string map [list %idx% [incr index]] $template]
dict set map $field [list ::apply $body]
}
uplevel 1 [list namespace ensemble create -command $recname -map $map -parameters rec]
}
proc ::argstest {args} {
package require cmdline
}
proc ::pattern::objects {} {
set result [::list]
foreach ns [namespace children ::pp] {
#lappend result [::list [namespace tail $ns] [set ${ns}::(self)]]
set ch [namespace tail $ns]
if {[string range $ch 0 2] eq "Obj"} {
set OID [string range $ch 3 end] ;#OID need not be digits (!?)
lappend result [::list $OID [list OID $OID object_command [set pp::${ch}::v_object_command] usedby [array names ${ns}::_iface::o_usedby]]]
}
}
return $result
}
proc ::pattern::name {num} {
#!todo - fix
#set ::p::${num}::(self)
lassign [interp alias {} ::p::$num] _predator info
if {![string length $_predator$info]} {
error "No object found for num:$num (no interp alias for ::p::$num)"
}
set invocants [dict get $info i]
set invocants_with_role_this [dict get $invocants this]
set invocant_this [lindex $invocants_with_role_this 0]
#lassign $invocant_this id info
#set map [dict get $info map]
#set fields [lindex $map 0]
lassign $invocant_this _id _ns _defaultmethod name _etc
return $name
}
proc ::pattern::with {cmd script} {
foreach c [info commands ::p::-1::*] {
interp alias {} [namespace tail $c] {} $c $cmd
}
interp alias {} . {} $cmd .
interp alias {} .. {} $cmd ..
return [uplevel 1 $script]
}
#system diagnostics etc
proc ::pattern::varspace_list {IID} {
namespace upvar ::p::${IID}::_iface o_varspace o_varspace o_variables o_variables
set varspaces [list]
dict for {vname vdef} $o_variables {
set vs [dict get $vdef varspace]
if {$vs ni $varspaces} {
lappend varspaces $vs
}
}
if {$o_varspace ni $varspaces} {
lappend varspaces $o_varspace
}
return $varspaces
}
proc ::pattern::check_interfaces {} {
foreach ns [namespace children ::p] {
set IID [namespace tail $ns]
if {[string is digit $IID]} {
foreach ref [array names ${ns}::_iface::o_usedby] {
set OID [string range $ref 1 end]
if {![namespace exists ::p::${OID}::_iface]} {
puts -nonewline stdout "\r\nPROBLEM!!!!!!!!! nonexistant/invalid object $OID referenced by Interface $IID\r\n"
} else {
puts -nonewline stdout .
}
#if {![info exists ::p::${OID}::(self)]} {
# puts "PROBLEM!!!!!!!!! nonexistant object $OID referenced by Interface $IID"
#}
}
}
}
puts -nonewline stdout "\r\n"
}
#from: http://wiki.tcl.tk/8766 (Introspection on aliases)
#usedby: metaface-1.1.6+
#required because aliases can be renamed.
#A renamed alias will still return it's target with 'interp alias {} oldname'
# - so given newname - we require which_alias to return the same info.
proc ::pattern::which_alias {cmd} {
uplevel 1 [list ::trace add execution $cmd enterstep ::error]
catch {uplevel 1 $cmd} res
uplevel 1 [list ::trace remove execution $cmd enterstep ::error]
#puts stdout "which_alias $cmd returning '$res'"
return $res
}
# [info args] like proc following an alias recursivly until it reaches
# the proc it originates from or cannot determine it.
# accounts for default parameters set by interp alias
#
proc ::pattern::aliasargs {cmd} {
set orig $cmd
set defaultargs [list]
# loop until error or return occurs
while {1} {
# is it a proc already?
if {[string equal [info procs $cmd] $cmd]} {
set result [info args $cmd]
# strip off the interp set default args
return [lrange $result [llength $defaultargs] end]
}
# is it a built in or extension command we can get no args for?
if {![string equal [info commands $cmd] $cmd]} {
error "\"$orig\" isn't a procedure"
}
# catch bogus cmd names
if {[lsearch [interp aliases {}] $cmd]==-1} {
if {[catch {::pattern::which_alias $cmd} alias]} {
error "\"$orig\" isn't a procedure or alias or command"
}
#set cmd [lindex $alias 0]
if {[llength $alias]>1} {
set cmd [lindex $alias 0]
set defaultargs [concat [lrange $alias 1 end] $defaultargs]
} else {
set cmd $alias
}
} else {
if {[llength [set cmdargs [interp alias {} $cmd]]]>0} {
# check if it is aliased in from another interpreter
if {[catch {interp target {} $cmd} msg]} {
error "Cannot resolve \"$orig\", alias leads to another interpreter."
}
if {$msg != {} } {
error "Not recursing into slave interpreter \"$msg\".\
\"$orig\" could not be resolved."
}
# check if defaults are set for the alias
if {[llength $cmdargs]>1} {
set cmd [lindex $cmdargs 0]
set defaultargs [concat [lrange $cmdargs 1 end] $defaultargs]
} else {
set cmd $cmdargs
}
}
}
}
}
proc ::pattern::aliasbody {cmd} {
set orig $cmd
set defaultargs [list]
# loop until error or return occurs
while {1} {
# is it a proc already?
if {[string equal [info procs $cmd] $cmd]} {
set result [info body $cmd]
# strip off the interp set default args
return $result
#return [lrange $result [llength $defaultargs] end]
}
# is it a built in or extension command we can get no args for?
if {![string equal [info commands $cmd] $cmd]} {
error "\"$orig\" isn't a procedure"
}
# catch bogus cmd names
if {[lsearch [interp aliases {}] $cmd]==-1} {
if {[catch {::pattern::which_alias $cmd} alias]} {
error "\"$orig\" isn't a procedure or alias or command"
}
#set cmd [lindex $alias 0]
if {[llength $alias]>1} {
set cmd [lindex $alias 0]
set defaultargs [concat [lrange $alias 1 end] $defaultargs]
} else {
set cmd $alias
}
} else {
if {[llength [set cmdargs [interp alias {} $cmd]]]>0} {
# check if it is aliased in from another interpreter
if {[catch {interp target {} $cmd} msg]} {
error "Cannot resolve \"$orig\", alias leads to another interpreter."
}
if {$msg != {} } {
error "Not recursing into slave interpreter \"$msg\".\
\"$orig\" could not be resolved."
}
# check if defaults are set for the alias
if {[llength $cmdargs]>1} {
set cmd [lindex $cmdargs 0]
set defaultargs [concat [lrange $cmdargs 1 end] $defaultargs]
} else {
set cmd $cmdargs
}
}
}
}
}
proc ::pattern::uniqueKey2 {} {
#!todo - something else??
return [clock seconds]-[incr ::pattern::idCounter]
}
#used by patternlib package
proc ::pattern::uniqueKey {} {
return [incr ::pattern::idCounter]
#uuid with tcllibc is about 30us compared with 2us
# for large datasets, e.g about 100K inserts this would be pretty noticable!
#!todo - uuid pool with background thread to repopulate when idle?
#return [uuid::uuid generate]
}
#-------------------------------------------------------------------------------------------------------------------------
proc ::pattern::test1 {} {
set msg "OK"
puts stderr "next line should say:'--- saystuff:$msg"
::>pattern .. Create ::>thing
::>thing .. PatternMethod saystuff args {
puts stderr "--- saystuff: $args"
}
::>thing .. Create ::>jjj
::>jjj . saystuff $msg
::>jjj .. Destroy
::>thing .. Destroy
}
proc ::pattern::test2 {} {
set msg "OK"
puts stderr "next line should say:'--- property 'stuff' value:$msg"
::>pattern .. Create ::>thing
::>thing .. PatternProperty stuff $msg
::>thing .. Create ::>jjj
puts stderr "--- property 'stuff' value:[::>jjj . stuff]"
::>jjj .. Destroy
::>thing .. Destroy
}
proc ::pattern::test3 {} {
set msg "OK"
puts stderr "next line should say:'--- property 'stuff' value:$msg"
::>pattern .. Create ::>thing
::>thing .. Property stuff $msg
puts stderr "--- property 'stuff' value:[::>thing . stuff]"
::>thing .. Destroy
}
#---------------------------------
#unknown/obsolete
#proc ::p::internals::showargs {args {ch stdout}} {puts $ch $args}
if {0} {
proc ::p::internals::new_interface {{usedbylist {}}} {
set OID [incr ::p::ID]
::p::internals::new_object ::p::ifaces::>$OID "" $OID
puts "obsolete >> new_interface created object $OID"
foreach usedby $usedbylist {
set ::p::${OID}::_iface::o_usedby(i$usedby) 1
}
set ::p::${OID}::_iface::o_varspace "" ;#default varspace is the object's namespace. (varspace is absolute if it has leading :: , otherwise it's a relative namespace below the object's namespace)
#NOTE - o_varspace is only the default varspace for when new methods/properties are added.
# it is possible to create some methods/props with one varspace value, then create more methods/props with a different varspace value.
set ::p::${OID}::_iface::o_constructor [list]
set ::p::${OID}::_iface::o_variables [list]
set ::p::${OID}::_iface::o_properties [dict create]
set ::p::${OID}::_iface::o_methods [dict create]
array set ::p::${OID}::_iface::o_definition [list]
set ::p::${OID}::_iface::o_open 1 ;#open for extending
return $OID
}
#temporary way to get OID - assumes single 'this' invocant
#!todo - make generic.
proc ::pattern::get_oid {_ID_} {
#puts stderr "#* get_oid: [lindex [dict get $_ID_ i this] 0 0]"
return [lindex [dict get $_ID_ i this] 0 0]
#set invocants [dict get $_ID_ i]
#set invocant_roles [dict keys $invocants]
#set role_members [dict get $invocants this]
##set this_invocant [lindex $role_members 0] ;#for the role 'this' we assume only one invocant in the list.
#set this_invocant [lindex [dict get $_ID_ i this] 0] ;
#lassign $this_invocant OID this_info
#
#return $OID
}
#compile the uncompiled level1 interface
#assert: no more than one uncompiled interface present at level1
proc ::p::meta::PatternCompile {self} {
????
upvar #0 $self SELFMAP
set ID [lindex $SELFMAP 0 0]
set patterns [lindex $SELFMAP 1 1] ;#list of level1 interfaces
set iid -1
foreach i $patterns {
if {[set ::p::${i}::_iface::o_open]} {
set iid $i ;#found it
break
}
}
if {$iid > -1} {
#!todo
::p::compile_interface $iid
set ::p::${iid}::_iface::o_open 0
} else {
#no uncompiled interface present at level 1. Do nothing.
return
}
}
proc ::p::meta::Def {self} {
error ::p::meta::Def
upvar #0 $self SELFMAP
set self_ID [lindex $SELFMAP 0 0]
set IFID [lindex $SELFMAP 1 0 end]
set maxc1 0
set maxc2 0
set arrName ::p::${IFID}::
upvar #0 $arrName state
array set methods {}
foreach nm [array names state] {
if {[regexp {^m-1,name,(.+)} $nm _match mname]} {
set methods($mname) [set state($nm)]
if {[string length $mname] > $maxc1} {
set maxc1 [string length $mname]
}
if {[string length [set state($nm)]] > $maxc2} {
set maxc2 [string length [set state($nm)]]
}
}
}
set bg1 [string repeat " " [expr {$maxc1 + 2}]]
set bg2 [string repeat " " [expr {$maxc2 + 2}]]
set r {}
foreach nm [lsort -dictionary [array names methods]] {
set arglist $state(m-1,args,$nm)
append r "[overtype::left $bg1 $nm] : [overtype::left $bg2 $methods($nm)] [::list $arglist]\n"
}
return $r
}
}

2590
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patternlib-1.2.6.tm vendored

File diff suppressed because it is too large Load Diff

754
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm vendored

@ -0,0 +1,754 @@
package provide patternpredator2 1.2.4
proc ::p::internals::jaws {OID _ID_ args} {
#puts stderr ">>>(patternpredator2 lib)jaws called with _ID_:$_ID_ args: $args"
#set OID [lindex [dict get $_ID_ i this] 0 0] ;#get_oid
yield
set w 1
set stack [list]
set wordcount [llength $args]
set terminals [list . .. , # @ !] ;#tokens which require the current stack to be evaluated first
set unsupported 0
set operator ""
set operator_prev "" ;#used only by argprotect to revert to previous operator
if {$OID ne "null"} {
#!DO NOT use upvar here for MAP! (calling set on a MAP in another iteration/call will overwrite a map for another object!)
#upvar #0 ::p::${OID}::_meta::map MAP
set MAP [set ::p::${OID}::_meta::map]
} else {
# error "jaws - OID = 'null' ???"
set MAP [list invocantdata [lindex [dict get $_ID_ i this] 0] ] ;#MAP taken from _ID_ will be missing 'interfaces' key
}
set invocantdata [dict get $MAP invocantdata]
lassign $invocantdata OID alias default_method object_command wrapped
set finished_args 0 ;#whether we've completely processed all args in the while loop and therefor don't need to peform the final word processing code
#don't use 'foreach word $args' - we sometimes need to backtrack a little by manipulating $w
while {$w < $wordcount} {
set word [lindex $args [expr {$w -1}]]
#puts stdout "w:$w word:$word stack:$stack"
if {$operator eq "argprotect"} {
set operator $operator_prev
lappend stack $word
incr w
} else {
if {[llength $stack]} {
if {$word in $terminals} {
set reduction [list 0 $_ID_ {*}$stack ]
#puts stderr ">>>jaws yielding value: $reduction triggered by word $word in position:$w"
set _ID_ [yield $reduction]
set stack [list]
#set OID [::pattern::get_oid $_ID_]
set OID [lindex [dict get $_ID_ i this] 0 0] ;#get_oid
if {$OID ne "null"} {
set MAP [set ::p::${OID}::_meta::map] ;#Do not use upvar here!
} else {
set MAP [list invocantdata [lindex [dict get $_ID_ i this] 0] interfaces [list level0 {} level1 {}]]
#puts stderr "WARNING REVIEW: jaws-branch - leave empty??????"
}
#review - 2018. switched to _ID_ instead of MAP
lassign [lindex [dict get $_ID_ i this] 0] OID alias default_method object_command
#lassign [dict get $MAP invocantdata] OID alias default_method object_command
#puts stdout "---->>> yielded _ID_: $_ID_ OID:$OID alias:$alias default_method:$default_method object_command:$object_command"
set operator $word
#don't incr w
#incr w
} else {
if {$operator eq "argprotect"} {
set operator $operator_prev
set operator_prev ""
lappend stack $word
} else {
#only look for leading argprotect chacter (-) if we're not already in argprotect mode
if {$word eq "--"} {
set operator_prev $operator
set operator "argprotect"
#Don't add the plain argprotector to the stack
} elseif {[string match "-*" $word]} {
#argSafety operator (tokens that appear to be Tcl 'options' automatically 'protect' the subsequent argument)
set operator_prev $operator
set operator "argprotect"
lappend stack $word
} else {
lappend stack $word
}
}
incr w
}
} else {
#no stack
switch -- $word {.} {
if {$OID ne "null"} {
#we know next word is a property or method of a pattern object
incr w
set nextword [lindex $args [expr {$w - 1}]]
set command ::p::${OID}::$nextword
set stack [list $command] ;#2018 j
set operator .
if {$w eq $wordcount} {
set finished_args 1
}
} else {
# don't incr w
#set nextword [lindex $args [expr {$w - 1}]]
set command $object_command ;#taken from the MAP
set stack [list "_exec_" $command]
set operator .
}
} {..} {
incr w
set nextword [lindex $args [expr {$w -1}]]
set command ::p::-1::$nextword
#lappend stack $command ;#lappend a small number of items to an empty list is slower than just setting the list.
set stack [list $command] ;#faster, and intent is clearer than lappend.
set operator ..
if {$w eq $wordcount} {
set finished_args 1
}
} {,} {
#puts stdout "Stackless comma!"
if {$OID ne "null"} {
set command ::p::${OID}::$default_method
} else {
set command [list $default_method $object_command]
#object_command in this instance presumably be a list and $default_method a list operation
#e.g "lindex {A B C}"
}
#lappend stack $command
set stack [list $command]
set operator ,
} {--} {
set operator_prev $operator
set operator argprotect
#no stack -
} {!} {
set command $object_command
set stack [list "_exec_" $object_command]
#puts stdout "!!!! !!!! $stack"
set operator !
} default {
if {$operator eq ""} {
if {$OID ne "null"} {
set command ::p::${OID}::$default_method
} else {
set command [list $default_method $object_command]
}
set stack [list $command]
set operator ,
lappend stack $word
} else {
#no stack - so we don't expect to be in argprotect mode already.
if {[string match "-*" $word]} {
#argSafety operator (tokens that appear to be Tcl 'options' automatically 'protect' the subsequent argument)
set operator_prev $operator
set operator "argprotect"
lappend stack $word
} else {
lappend stack $word
}
}
}
incr w
}
}
} ;#end while
#process final word outside of loop
#assert $w == $wordcount
#trailing operators or last argument
if {!$finished_args} {
set word [lindex $args [expr {$w -1}]]
if {$operator eq "argprotect"} {
set operator $operator_prev
set operator_prev ""
lappend stack $word
incr w
} else {
switch -- $word {.} {
if {![llength $stack]} {
#set stack [list "_result_" [::p::internals::ref_to_object $_ID_]]
yieldto return [::p::internals::ref_to_object $_ID_]
error "assert: never gets here"
} else {
#puts stdout "==== $stack"
#assert - whenever _ID_ changed in this proc - we have updated the $OID variable
yieldto return [::p::internals::ref_to_stack $OID $_ID_ $stack]
error "assert: never gets here"
}
set operator .
} {..} {
#trailing .. after chained call e.g >x . item 0 ..
#puts stdout "$$$$$$$$$$$$ [list 0 $_ID_ {*}$stack] $$$$"
#set reduction [list 0 $_ID_ {*}$stack]
yieldto return [yield [list 0 $_ID_ {*}$stack]]
} {#} {
set unsupported 1
} {,} {
set unsupported 1
} {&} {
set unsupported 1
} {@} {
set unsupported 1
} {--} {
#set reduction [list 0 $_ID_ {*}$stack[set stack [list]]]
#puts stdout " -> -> -> about to call yield $reduction <- <- <-"
set _ID_ [yield [list 0 $_ID_ {*}$stack[set stack [list]]] ]
#set OID [::pattern::get_oid $_ID_]
set OID [lindex [dict get $_ID_ i this] 0 0] ;#get_oid
if {$OID ne "null"} {
set MAP [set ::p::${OID}::_meta::map] ;#DO not use upvar here!
} else {
set MAP [list invocantdata [lindex [dict get $_ID_ i this] 0] interfaces {level0 {} level1 {}} ]
}
yieldto return $MAP
} {!} {
#error "untested branch"
set _ID_ [yield [list 0 $_ID_ {*}$stack[set stack [list]]]]
#set OID [::pattern::get_oid $_ID_]
set OID [lindex [dict get $_ID_ i this] 0 0] ;#get_oid
if {$OID ne "null"} {
set MAP [set ::p::${OID}::_meta::map] ;#DO not use upvar here!
} else {
set MAP [list invocantdata [lindex [dict get $_ID_ i this] 0] ]
}
lassign [dict get $MAP invocantdata] OID alias default_command object_command
set command $object_command
set stack [list "_exec_" $command]
set operator !
} default {
if {$operator eq ""} {
#error "untested branch"
lassign [dict get $MAP invocantdata] OID alias default_command object_command
#set command ::p::${OID}::item
set command ::p::${OID}::$default_command
lappend stack $command
set operator ,
}
#do not look for argprotect items here (e.g -option) as the final word can't be an argprotector anyway.
lappend stack $word
}
if {$unsupported} {
set unsupported 0
error "trailing '$word' not supported"
}
#if {$operator eq ","} {
# incr wordcount 2
# set stack [linsert $stack end-1 . item]
#}
incr w
}
}
#final = 1
#puts stderr ">>>jaws final return value: [list 1 $_ID_ {*}$stack]"
return [list 1 $_ID_ {*}$stack]
}
#trailing. directly after object
proc ::p::internals::ref_to_object {_ID_} {
set OID [lindex [dict get $_ID_ i this] 0 0]
upvar #0 ::p::${OID}::_meta::map MAP
lassign [dict get $MAP invocantdata] OID alias default_method object_command
set refname ::p::${OID}::_ref::__OBJECT
array set $refname [list] ;#important to initialise the variable as an array here - or initial read attempts on elements will not fire traces
set traceCmd [list ::p::predator::object_read_trace $OID $_ID_]
if {[list {read} $traceCmd] ni [trace info variable $refname]} {
#puts stdout "adding read trace on variable '$refname' - traceCmd:'$traceCmd'"
trace add variable $refname {read} $traceCmd
}
set traceCmd [list ::p::predator::object_array_trace $OID $_ID_]
if {[list {array} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {array} $traceCmd
}
set traceCmd [list ::p::predator::object_write_trace $OID $_ID_]
if {[list {write} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {write} $traceCmd
}
set traceCmd [list ::p::predator::object_unset_trace $OID $_ID_]
if {[list {unset} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {unset} $traceCmd
}
return $refname
}
proc ::p::internals::create_or_update_reference {OID _ID_ refname command} {
#if {[lindex $fullstack 0] eq "_exec_"} {
# #strip it. This instruction isn't relevant for a reference.
# set commandstack [lrange $fullstack 1 end]
#} else {
# set commandstack $fullstack
#}
#set argstack [lassign $commandstack command]
#set field [string map {> __OBJECT_} [namespace tail $command]]
set reftail [namespace tail $refname]
set argstack [lassign [split $reftail +] field]
set field [string map {> __OBJECT_} [namespace tail $command]]
#puts stderr "refname:'$refname' command: $command field:$field"
if {$OID ne "null"} {
upvar #0 ::p::${OID}::_meta::map MAP
} else {
#set map [dict get [lindex [dict get $_ID_ i this] 0 1] map]
set MAP [list invocantdata [lindex [dict get $_ID_ i this] 0] interfaces {level0 {} level1 {}}]
}
lassign [dict get $MAP invocantdata] OID alias default_method object_command
if {$OID ne "null"} {
interp alias {} $refname {} $command $_ID_ {*}$argstack
} else {
interp alias {} $refname {} $command {*}$argstack
}
#set iflist [lindex $map 1 0]
set iflist [dict get $MAP interfaces level0]
#set iflist [dict get $MAP interfaces level0]
set field_is_property_like 0
foreach IFID [lreverse $iflist] {
#tcl (braced) expr has lazy evaluation for &&, || & ?: operators - so this should be reasonably efficient.
if {[llength [info commands ::p::${IFID}::_iface::(GET)$field]] || [llength [info commands ::p::${IFID}::_iface::(SET)$field]]} {
set field_is_property_like 1
#There is a setter or getter (but not necessarily an entry in the o_properties dict)
break
}
}
#whether field is a property or a method - remove any commandrefMisuse_TraceHandler
foreach tinfo [trace info variable $refname] {
#puts "-->removing traces on $refname: $tinfo"
if {[lindex $tinfo 1 0] eq "::p::internals::commandrefMisuse_TraceHandler"} {
trace remove variable $refname {*}$tinfo
}
}
if {$field_is_property_like} {
#property reference
set this_invocantdata [lindex [dict get $_ID_ i this] 0]
lassign $this_invocantdata OID _alias _defaultmethod object_command
#get fully qualified varspace
#
set propdict [$object_command .. GetPropertyInfo $field]
if {[dict exist $propdict $field]} {
set field_is_a_property 1
set propinfo [dict get $propdict $field]
set varspace [dict get $propinfo varspace]
if {$varspace eq ""} {
set full_varspace ::p::${OID}
} else {
if {[::string match "::*" $varspace]} {
set full_varspace $varspace
} else {
set full_varspace ::p::${OID}::$varspace
}
}
} else {
set field_is_a_property 0
#no propertyinfo - this field was probably established as a PropertyRead and/or PropertyWrite without a Property
#this is ok - and we still set the trace infrastructure below (app may convert it to a normal Property later)
set full_varspace ::p::${OID}
}
#We only trace on entire property.. not array elements (if references existed to both the array and an element both traces would be fired -(entire array trace first))
set Hndlr [::list ::p::predator::propvar_write_TraceHandler $OID $field]
if { [::list {write} $Hndlr] ni [trace info variable ${full_varspace}::o_${field}]} {
trace add variable ${full_varspace}::o_${field} {write} $Hndlr
}
set Hndlr [::list ::p::predator::propvar_unset_TraceHandler $OID $field]
if { [::list {unset} $Hndlr] ni [trace info variable ${full_varspace}::o_${field}]} {
trace add variable ${full_varspace}::o_${field} {unset} $Hndlr
}
#supply all data in easy-access form so that propref_trace_read is not doing any extra work.
set get_cmd ::p::${OID}::(GET)$field
set traceCmd [list ::p::predator::propref_trace_read $get_cmd $_ID_ $refname $field $argstack]
if {[list {read} $traceCmd] ni [trace info variable $refname]} {
set fieldvarname ${full_varspace}::o_${field}
#synch the refvar with the real var if it exists
#catch {set $refname [$refname]}
if {[array exists $fieldvarname]} {
if {![llength $argstack]} {
#unindexed reference
array set $refname [array get $fieldvarname]
#upvar $fieldvarname $refname
} else {
set s0 [lindex $argstack 0]
#refs to nonexistant array members common? (catch vs 'info exists')
if {[info exists ${fieldvarname}($s0)]} {
set $refname [set ${fieldvarname}($s0)]
}
}
} else {
#refs to uninitialised props actually should be *very* common.
#If we use 'catch', it means retrieving refs to non-initialised props is slower. Fired catches can be relatively expensive.
#Because it's common to get a ref to uninitialised props (e.g for initial setting of their value) - we will use 'info exists' instead of catch.
#set errorInfo_prev $::errorInfo ;#preserve errorInfo across catches!
#puts stdout " ---->>!!! ref to uninitialised prop $field $argstack !!!<------"
if {![llength $argstack]} {
#catch {set $refname [set ::p::${OID}::o_$field]}
if {[info exists $fieldvarname]} {
set $refname [set $fieldvarname]
#upvar $fieldvarname $refname
}
} else {
if {[llength $argstack] == 1} {
#catch {set $refname [lindex [set ::p::${OID}::o_$field] [lindex $argstack 0]]}
if {[info exists $fieldvarname]} {
set $refname [lindex [set $fieldvarname] [lindex $argstack 0]]
}
} else {
#catch {set $refname [lindex [set ::p::${OID}::o_$field] $argstack]}
if {[info exists $fieldvarname]} {
set $refname [lindex [set $fieldvarname] $argstack]
}
}
}
#! what if someone has put a trace on ::errorInfo??
#set ::errorInfo $errorInfo_prev
}
trace add variable $refname {read} $traceCmd
set traceCmd [list ::p::predator::propref_trace_write $_ID_ $OID $full_varspace $refname]
trace add variable $refname {write} $traceCmd
set traceCmd [list ::p::predator::propref_trace_unset $_ID_ $OID $refname]
trace add variable $refname {unset} $traceCmd
set traceCmd [list ::p::predator::propref_trace_array $_ID_ $OID $refname]
# puts "**************** installing array variable trace on ref:$refname - cmd:$traceCmd"
trace add variable $refname {array} $traceCmd
}
} else {
#puts "$refname ====> adding refMisuse_traceHandler $alias $field"
#matching variable in order to detect attempted use as property and throw error
#2018
#Note that we are adding a trace on a variable (the refname) which does not exist.
#this is fine - except that the trace won't fire for attempt to write it as an array using syntax such as set $ref(someindex)
#we could set the ref to an empty array - but then we have to also undo this if a property with matching name is added
##array set $refname {} ;#empty array
# - the empty array would mean a slightly better error message when misusing a command ref as an array
#but this seems like a code complication for little benefit
#review
trace add variable $refname {read write unset array} [list ::p::internals::commandrefMisuse_TraceHandler $OID $field]
}
}
#trailing. after command/property
proc ::p::internals::ref_to_stack {OID _ID_ fullstack} {
if {[lindex $fullstack 0] eq "_exec_"} {
#strip it. This instruction isn't relevant for a reference.
set commandstack [lrange $fullstack 1 end]
} else {
set commandstack $fullstack
}
set argstack [lassign $commandstack command]
set field [string map {> __OBJECT_} [namespace tail $command]]
#!todo?
# - make every object's OID unpredictable and sparse (UUID) and modify 'namespace child' etc to prevent iteration/inspection of ::p namespace.
# - this would only make sense for an environment where any meta methods taking a code body (e.g .. Method .. PatternMethod etc) are restricted.
#references created under ::p::${OID}::_ref are effectively inside a 'varspace' within the object itself.
# - this would in theory allow a set of interface functions on the object which have direct access to the reference variables.
set refname ::p::${OID}::_ref::[join [concat $field $argstack] +]
if {[llength [info commands $refname]]} {
#todo - review - what if the field changed to/from a property/method?
#probably should fix that where such a change is made and leave this short circuit here to give reasonable performance for existing refs
return $refname
}
::p::internals::create_or_update_reference $OID $_ID_ $refname $command
return $refname
}
namespace eval pp {
variable operators [list .. . -- - & @ # , !]
variable operators_notin_args ""
foreach op $operators {
append operators_notin_args "({$op} ni \$args) && "
}
set operators_notin_args [string trimright $operators_notin_args " &"] ;#trim trailing spaces and ampersands
#set operators_notin_args {({.} ni $args) && ({,} ni $args) && ({..} ni $args)}
}
interp alias {} strmap {} string map ;#stop code editor from mono-colouring our big string mapped code blocks!
# 2017 ::p::predator2 is the development version - intended for eventual use as the main dispatch mechanism.
#each map is a 2 element list of lists.
# form: {$commandinfo $interfaceinfo}
# commandinfo is of the form: {ID Namespace defaultmethod commandname _?}
#2018
#each map is a dict.
#form: {invocantdata {ID Namespace defaultmethod commandname _?} interfaces {level0 {} level1 {}}}
#OID = Object ID (integer for now - could in future be a uuid)
proc ::p::predator2 {_ID_ args} {
#puts stderr "predator2: _ID_:'$_ID_' args:'$args'"
#set invocants [dict get $_ID_ i]
#set invocant_roles [dict keys $invocants]
#For now - we are 'this'-centric (single dispatch). todo - adapt for multiple roles, multimethods etc.
#set this_role_members [dict get $invocants this]
#set this_invocant [lindex [dict get $_ID_ i this] 0] ;#for the role 'this' we assume only one invocant in the list.
#lassign $this_invocant this_OID this_info_dict
set this_OID [lindex [dict get $_ID_ i this] 0 0] ;#get_oid
set cheat 1 ;#
#-------
#Optimise the next most common use case. A single . followed by args which contain no other operators (non-chained call)
#(it should be functionally equivalent to remove this shortcut block)
if {$cheat} {
if { ([lindex $args 0] eq {.}) && ([llength $args] > 1) && ([llength [lsearch -all -inline $args .]] == 1) && ({,} ni $args) && ({..} ni $args) && ({--} ni $args) && ({!} ni $args)} {
set remaining_args [lassign $args dot method_or_prop]
#how will we do multiple apis? (separate interface stacks) apply? apply [list [list _ID_ {*}$arglist] ::p::${stackid?}::$method_or_prop ::p::${this_OID}] ???
set command ::p::${this_OID}::$method_or_prop
#REVIEW!
#e.g what if the method is named "say hello" ?? (hint - it will break because we will look for 'say')
#if {[llength $command] > 1} {
# error "methods with spaces not included in test suites - todo fix!"
#}
#Dont use {*}$command - (so we can support methods with spaces)
#if {![llength [info commands $command]]} {}
if {[namespace which $command] eq ""} {
if {[namespace which ::p::${this_OID}::(UNKNOWN)] ne ""} {
#lset command 0 ::p::${this_OID}::(UNKNOWN) ;#seems wrong - command could have spaces
set command ::p::${this_OID}::(UNKNOWN)
#tailcall {*}$command $_ID_ $cmdname {*}[lrange $args 2 end] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
tailcall $command $_ID_ $method_or_prop {*}[lrange $args 2 end] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
return -code error -errorinfo "(::p::predator2) error running command:'$command' argstack:'[lrange $args 2 end]'\n - command not found and no 'unknown' handler" "method '$method_or_prop' not found"
}
} else {
#tailcall {*}$command $_ID_ {*}$remaining_args
tailcall $command $_ID_ {*}$remaining_args
}
}
}
#------------
if {([llength $args] == 1) && ([lindex $args 0] eq "..")} {
return $_ID_
}
#puts stderr "pattern::predator (test version) called with: _ID_:$_ID_ args:$args"
#puts stderr "this_info_dict: $this_info_dict"
if {![llength $args]} {
#should return some sort of public info.. i.e probably not the ID which is an implementation detail
#return cmd
return [lindex [dict get [set ::p::${this_OID}::_meta::map] invocantdata] 0] ;#Object ID
#return a dict keyed on object command name - (suitable as use for a .. Create 'target')
#lassign [dict get [set ::p::${this_OID}::_meta::map] invocantdata] this_OID alias default_method object_command wrapped
#return [list $object_command [list -id $this_OID ]]
} elseif {[llength $args] == 1} {
#short-circuit the single index case for speed.
if {[lindex $args 0] ni {.. . -- - & @ # , !}} {
#lassign [dict get [set ::p::${this_OID}::_meta::map] invocantdata] this_OID alias default_method
lassign [lindex [dict get $_ID_ i this] 0] this_OID alias default_method
tailcall ::p::${this_OID}::$default_method $_ID_ [lindex $args 0]
} elseif {[lindex $args 0] eq {--}} {
#!todo - we could hide the invocant by only allowing this call from certain uplevel procs..
# - combined with using UUIDs for $OID, and a secured/removed metaface on the object
# - (and also hiding of [interp aliases] command so they can't iterate and examine all aliases)
# - this could effectively hide the object's namespaces,vars etc from the caller (?)
return [set ::p::${this_OID}::_meta::map]
}
}
#upvar ::p::coroutine_instance c ;#coroutine names must be unique per call to predator (not just per object - or we could get a clash during some cyclic calls)
#incr c
#set reduce ::p::reducer${this_OID}_$c
set reduce ::p::reducer${this_OID}_[incr ::p::coroutine_instance]
#puts stderr "..................creating reducer $reduce with args $this_OID _ID_ $args"
coroutine $reduce ::p::internals::jaws $this_OID $_ID_ {*}$args
set current_ID_ $_ID_
set final 0
set result ""
while {$final == 0} {
#the argument given here to $reduce will be returned by 'yield' within the coroutine context (jaws)
set reduction_args [lassign [$reduce $current_ID_[set current_ID_ [list]] ] final current_ID_ command]
#puts stderr "..> final:$final current_ID_:'$current_ID_' command:'$command' reduction_args:'$reduction_args'"
#if {[string match *Destroy $command]} {
# puts stdout " calling Destroy reduction_args:'$reduction_args'"
#}
if {$final == 1} {
if {[llength $command] == 1} {
if {$command eq "_exec_"} {
tailcall {*}$reduction_args
}
if {[llength [info commands $command]]} {
tailcall {*}$command $current_ID_ {*}$reduction_args
}
set cmdname [namespace tail $command]
set this_OID [lindex [dict get $current_ID_ i this] 0 0]
if {[llength [info commands ::p::${this_OID}::(UNKNOWN)]]} {
lset command 0 ::p::${this_OID}::(UNKNOWN)
tailcall {*}$command $current_ID_ $cmdname {*}$reduction_args ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
return -code error -errorinfo "1)error running command:'$command' argstack:'$reduction_args'\n - command not found and no 'unknown' handler" "method '$cmdname' not found"
}
} else {
#e.g lindex {a b c}
tailcall {*}$command {*}$reduction_args
}
} else {
if {[lindex $command 0] eq "_exec_"} {
set result [uplevel 1 [list {*}[lrange $command 1 end] {*}$reduction_args]]
set current_ID_ [list i [list this [list [list "null" {} {lindex} $result {} ] ] ] context {} ]
} else {
if {[llength $command] == 1} {
if {![llength [info commands $command]]} {
set cmdname [namespace tail $command]
set this_OID [lindex [dict get $current_ID_ i this] 0 0]
if {[llength [info commands ::p::${this_OID}::(UNKNOWN)]]} {
lset command 0 ::p::${this_OID}::(UNKNOWN)
set result [uplevel 1 [list {*}$command $current_ID_ $cmdname {*}$reduction_args]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
return -code error -errorinfo "2)error running command:'$command' argstack:'$reduction_args'\n - command not found and no 'unknown' handler" "method '$cmdname' not found"
}
} else {
#set result [uplevel 1 [list {*}$command $current_ID_ {*}$reduction_args ]]
set result [uplevel 1 [list {*}$command $current_ID_ {*}$reduction_args ]]
}
} else {
set result [uplevel 1 [list {*}$command {*}$reduction_args]]
}
if {[llength [info commands $result]]} {
if {([llength $result] == 1) && ([string first ">" [namespace tail $result]] == 0)} {
#looks like a pattern command
set current_ID_ [$result .. INVOCANTDATA]
#todo - determine if plain .. INVOCANTDATA is sufficient instead of .. UPDATEDINVOCANTDATA
#if {![catch {$result .. INVOCANTDATA} result_invocantdata]} {
# set current_ID_ $result_invocantdata
#} else {
# return -code error -errorinfo "3)error running command:'$command' argstack:'$reduction_args'\n - Failed to access result:'$result' as a pattern object." "Failed to access result:'$result' as a pattern object"
#}
} else {
#non-pattern command
set current_ID_ [list i [list this [list [list "null" {} {lindex} $result {} ] ] ] context {}]
}
} else {
set current_ID_ [list i [list this [list [list "null" {} {lindex} $result {} ] ] ] context {}]
#!todo - allow further operations on non-command values. e.g dicts, lists & strings (treat strings as lists)
}
}
}
}
error "Assert: Shouldn't get here (end of ::p::predator2)"
#return $result
}

1311
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/promise-1.2.0.tm vendored

File diff suppressed because it is too large Load Diff

8187
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk-0.1.tm vendored

File diff suppressed because it is too large Load Diff

290
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm vendored

@ -0,0 +1,290 @@
# -*- 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) 2024
#
# @@ Meta Begin
# Application punk::aliascore 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::aliascore 0 0.1.0]
#[copyright "2024"]
#[titledesc {punkshell command aliases}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::aliascore]
#[keywords module alias]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::aliascore
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::aliascore
#[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::aliascore::class {
# #*** !doctools
# #[subsection {Namespace punk::aliascore::class}]
# #[para] class definitions
# if {[info commands [namespace current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::aliascore {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable aliases
#use absolute ns ie must be prefixed with ::
#single element commands are imported if source command already exists, otherwise aliased. multi element commands are aliased
#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\
list_as_lines ::punk::lib::list_as_lines\
lines_as_list ::punk::lib::lines_as_list\
linelist ::punk::lib::linelist\
linesort ::punk::lib::linesort\
pdict ::punk::lib::pdict\
plist {::punk::lib::pdict -roottype list}\
showlist {::punk::lib::showdict -roottype list}\
rehash ::punk::rehash\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::ansistrip\
stripansi ::punk::ansi::ansistrip\
ansiwrap ::punk::ansi::ansiwrap\
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}\
smcup ::punk::console::enable_alt_screen\
rmcup ::punk::console::disable_alt_screen\
]
#*** !doctools
#[subsection {Namespace punk::aliascore}]
#[para] Core API functions for punk::aliascore
#[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"
#}
#todo - options as to whether we should raise an error if collisions found, undo aliases etc?
proc init {args} {
set defaults {-force 0}
set opts [dict merge $defaults $args]
set opt_force [dict get $opts -force]
#we never override existing aliases to ::repl::interp* even if -force = 1
#(these are our safebase aliases)
set ignore_pattern "::repl::interp*"
set ignore_aliases [list]
variable aliases
set existing [list]
set conflicts [list]
foreach {a cmd} $aliases {
if {[tcl::info::commands ::$a] ne ""} {
lappend existing $a
set existing_alias [interp alias "" $a]
if {$existing_alias ne ""} {
set existing_target $existing_alias
if {[string match $ignore_pattern $existing_target]} {
#don't consider it a conflict - will use ignore_aliases to exclude it below
lappend ignore_aliases $a
continue
}
} else {
if {[catch {tcl::namespace::origin $a} existing_command]} {
set existing_command ""
}
set existing_target $existing_command
}
if {$existing_target ne $cmd} {
#command exists in global ns but doesn't match our defined aliases/imports
lappend conflicts $a
}
}
}
if {!$opt_force && [llength $conflicts]} {
error "punk::aliascore::init declined to create any aliases or imports because -force == 0 and conflicts found:$conflicts"
}
set tempns ::temp_[info cmdcount] ;#temp ns for renames
dict for {a cmd} $aliases {
#puts "aliascore $a -> $cmd"
if {$a in $ignore_aliases} {
continue
}
if {[llength $cmd] > 1} {
interp alias {} $a {} {*}$cmd
} else {
if {[tcl::info::commands $cmd] ne ""} {
#todo - ensure exported? noclobber?
if {[tcl::namespace::tail $a] eq [tcl::namespace::tail $cmd]} {
#puts stderr "importing $cmd"
tcl::namespace::eval :: [list namespace import $cmd]
} else {
#target command name differs from exported name
#e.g stripansi -> punk::ansi::ansistrip
#import and rename
#puts stderr "importing $cmd (with rename to ::$a)"
tcl::namespace::eval $tempns [list namespace import $cmd]
catch {rename ${tempns}::[namespace tail $cmd] ::$a}
}
} else {
interp alias {} $a {} {*}$cmd
}
}
}
#tcl::namespace::delete $tempns
return [dict create aliases [dict keys $aliases] unchanged $ignore_aliases changed $conflicts]
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::aliascore ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#interp alias {} list_as_lines {} punk::lib::list_as_lines
#interp alias {} lines_as_list {} punk::lib::lines_as_list
#interp alias {} ansistrip {} punk::ansi::ansistrip ;#review
#interp alias {} linelist {} punk::lib::linelist ;#critical for = assignment features
#interp alias {} linesort {} punk::lib::linesort
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::aliascore::lib {
namespace export {[a-z]*} ;# Convention: export all lowercase
namespace path [namespace parent]
#*** !doctools
#[subsection {Namespace punk::aliascore::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::aliascore::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
namespace eval punk::aliascore::system {
#*** !doctools
#[subsection {Namespace punk::aliascore::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::aliascore [namespace eval punk::aliascore {
variable pkg punk::aliascore
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

2498
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm vendored

File diff suppressed because it is too large Load Diff

5395
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm vendored

File diff suppressed because it is too large Load Diff

48
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/assertion-0.1.0.tm vendored

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # 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.2.tm # 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. # 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. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,10 +18,10 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::assertion 0 0.1.0] #[manpage_begin punkshell_module_punk::assertion 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {assertion alternative to control::assert}] [comment {-- Name section and table of contents description --}] #[titledesc {assertion alternative to control::assert}] [comment {-- Name section and table of contents description --}]
#[moddesc {per-namespace assertions with }] [comment {-- Description at end of page heading --}] #[moddesc {per-namespace assertions with }] [comment {-- Description at end of page heading --}]
#[require punk::assertion] #[require punk::assertion]
#[keywords module assertion assert debug] #[keywords module assertion assert debug]
#[description] #[description]
@ -99,9 +99,9 @@ tcl::namespace::eval punk::assertion::class {
} }
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#keep 2 namespaces for assertActive and assertInactive so there is introspection available via namespace origin #keep 2 namespaces for assertActive and assertInactive so there is introspection available via namespace origin
tcl::namespace::eval punk::assertion::primary { tcl::namespace::eval punk::assertion::primary {
#tcl::namespace::export {[a-z]*} #tcl::namespace::export {[a-z]*}
tcl::namespace::export assertActive assertInactive tcl::namespace::export assertActive assertInactive
proc assertActive {expr args} { proc assertActive {expr args} {
@ -112,7 +112,7 @@ tcl::namespace::eval punk::assertion::primary {
if {![tcl::string::is boolean -strict $res]} { if {![tcl::string::is boolean -strict $res]} {
return -code error "invalid boolean expression: $expr" return -code error "invalid boolean expression: $expr"
} }
if {$res} {return} if {$res} {return}
if {[llength $args]} { if {[llength $args]} {
@ -130,9 +130,9 @@ tcl::namespace::eval punk::assertion::primary {
} }
tcl::namespace::eval punk::assertion::secondary { tcl::namespace::eval punk::assertion::secondary {
tcl::namespace::export * tcl::namespace::export *
#we need to actually define these procs here, (not import then re-export) - or namespace origin will report the original source namespace - which isn't what we want. #we need to actually define these procs here, (not import then re-export) - or namespace origin will report the original source namespace - which isn't what we want.
proc assertActive {expr args} [tcl::info::body ::punk::assertion::primary::assertActive] proc assertActive {expr args} [tcl::info::body ::punk::assertion::primary::assertActive]
proc assertInactive args {} proc assertInactive args {}
} }
@ -151,7 +151,7 @@ tcl::namespace::eval punk::assertion {
} }
do_ns_import do_ns_import
#puts --------BBB #puts --------BBB
rename assertActive assert rename assertActive assert
} }
@ -162,20 +162,20 @@ tcl::namespace::eval punk::assertion {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::assertion}] #[subsection {Namespace punk::assertion}]
#[para] Core API functions for punk::assertion #[para] Core API functions for punk::assertion
#[list_begin definitions] #[list_begin definitions]
#proc sample1 {p1 n args} { #proc sample1 {p1 n args} {
# #*** !doctools # #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]] # #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1 # #[para]Description of sample1
# #[para] Arguments: # #[para] Arguments:
# # [list_begin arguments] # # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1. # # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n. # # [arg_def integer n] A description of integer argument n.
# # [list_end] # # [list_end]
# return "ok" # return "ok"
#} #}
#like tcllib's control::assert - we are limited to the same callback for all namespaces. #like tcllib's control::assert - we are limited to the same callback for all namespaces.
@ -218,7 +218,7 @@ tcl::namespace::eval punk::assertion {
if {$on_off} { if {$on_off} {
#Enable it in calling namespace #Enable it in calling namespace
if {"assert" eq $info_command} { if {"assert" eq $info_command} {
#There is an assert command reachable - due to namespace path etc, it could be in another namespace entirely - (not necessarily in an ancestor namespace of the namespace's tree structure) #There is an assert command reachable - due to namespace path etc, it could be in another namespace entirely - (not necessarily in an ancestor namespace of the namespace's tree structure)
if {$which_assert eq [punk::assertion::system::nsjoin ${nscaller} assert]} { if {$which_assert eq [punk::assertion::system::nsjoin ${nscaller} assert]} {
tcl::namespace::eval $nscaller { tcl::namespace::eval $nscaller {
set assertorigin [tcl::namespace::origin assert] set assertorigin [tcl::namespace::origin assert]
@ -243,7 +243,7 @@ tcl::namespace::eval punk::assertion {
} }
return 1 return 1
} else { } else {
#assert is available, but isn't in the calling namespace - we should enable it in a way that is distinguishable from case where assert was explicitly imported to this namespace #assert is available, but isn't in the calling namespace - we should enable it in a way that is distinguishable from case where assert was explicitly imported to this namespace
tcl::namespace::eval $nscaller { tcl::namespace::eval $nscaller {
set assertorigin [tcl::namespace::origin assert] set assertorigin [tcl::namespace::origin assert]
if {[tcl::string::match ::punk::assertion::* $assertorigin]} { if {[tcl::string::match ::punk::assertion::* $assertorigin]} {
@ -303,8 +303,8 @@ tcl::namespace::eval punk::assertion {
return 0 return 0
} }
} else { } else {
#no assert command reachable #no assert command reachable
#If caller is using assert in this namespace - they should have imported it, or ensured it was reachable via namespace path #If caller is using assert in this namespace - they should have imported it, or ensured it was reachable via namespace path
puts stderr "no assert command visible from namespace '$nscaller' - use: namespace import ::punk::assertion::assert" puts stderr "no assert command visible from namespace '$nscaller' - use: namespace import ::punk::assertion::assert"
return 0 return 0
} }
@ -327,14 +327,14 @@ tcl::namespace::eval punk::assertion::lib {
tcl::namespace::path [tcl::namespace::parent] tcl::namespace::path [tcl::namespace::parent]
#*** !doctools #*** !doctools
#[subsection {Namespace punk::assertion::lib}] #[subsection {Namespace punk::assertion::lib}]
#[para] Secondary functions that are part of the API #[para] Secondary functions that are part of the API
#[list_begin definitions] #[list_begin definitions]
#proc utility1 {p1 args} { #proc utility1 {p1 args} {
# #*** !doctools # #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] # #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1 # #[para]Description of utility1
# return 1 # return 1
#} #}
@ -352,7 +352,7 @@ tcl::namespace::eval punk::assertion::lib {
tcl::namespace::eval punk::assertion::system { tcl::namespace::eval punk::assertion::system {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::assertion::system}] #[subsection {Namespace punk::assertion::system}]
#[para] Internal functions that are not part of the API #[para] Internal functions that are not part of the API
#Maintenance - snarfed from punk::ns to reduce dependencies - punk::ns::nsprefix is the master version #Maintenance - snarfed from punk::ns to reduce dependencies - punk::ns::nsprefix is the master version
#nsprefix/nstail are string functions - they do not concern themselves with what namespaces are present in the system #nsprefix/nstail are string functions - they do not concern themselves with what namespaces are present in the system
@ -375,7 +375,7 @@ tcl::namespace::eval punk::assertion::system {
proc nstail {nspath args} { proc nstail {nspath args} {
#normalize the common case of :::: #normalize the common case of ::::
set nspath [tcl::string::map [list :::: ::] $nspath] set nspath [tcl::string::map [list :::: ::] $nspath]
set mapped [tcl::string::map [list :: \u0FFF] $nspath] set mapped [tcl::string::map [list :: \u0FFF] $nspath]
set parts [split $mapped \u0FFF] set parts [split $mapped \u0FFF]
set defaults [list -strict 0] set defaults [list -strict 0]
@ -411,11 +411,11 @@ tcl::namespace::eval punk::assertion::system {
} }
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::assertion [tcl::namespace::eval punk::assertion { package provide punk::assertion [tcl::namespace::eval punk::assertion {
variable pkg punk::assertion variable pkg punk::assertion
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

60
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap-0.1.0.tm vendored

@ -26,7 +26,7 @@
#[para]punk::cap provides management of named capabilities and the provider packages and handler packages that implement a pluggable capability. #[para]punk::cap provides management of named capabilities and the provider packages and handler packages that implement a pluggable capability.
#[para]see also [uri https://core.tcl-lang.org/tcllib/doc/trunk/embedded/md/tcllib/files/modules/pluginmgr/pluginmgr.md {tcllib pluginmgr}] for an alternative which uses safe interpreters #[para]see also [uri https://core.tcl-lang.org/tcllib/doc/trunk/embedded/md/tcllib/files/modules/pluginmgr/pluginmgr.md {tcllib pluginmgr}] for an alternative which uses safe interpreters
#[subsection Concepts] #[subsection Concepts]
#[para]A [term capability] may be something like providing a folder of files, or just a data dictionary, and/or an API #[para]A [term capability] may be something like providing a folder of files, or just a data dictionary, and/or an API
# #
#[para][term {capability handler}] - a package/namespace which may provide validation and standardised ways of looking up provider data #[para][term {capability handler}] - a package/namespace which may provide validation and standardised ways of looking up provider data
# registered (or not) using register_capabilityname <capname> <capnamespace> # registered (or not) using register_capabilityname <capname> <capnamespace>
@ -49,7 +49,7 @@ package require oolib
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::cap { tcl::namespace::eval punk::cap {
variable pkgcapsdeclared [tcl::dict::create] variable pkgcapsdeclared [tcl::dict::create]
variable pkgcapsaccepted [tcl::dict::create] variable pkgcapsaccepted [tcl::dict::create]
variable caps [tcl::dict::create] variable caps [tcl::dict::create]
namespace eval class { namespace eval class {
@ -71,8 +71,8 @@ tcl::namespace::eval punk::cap {
#*** !doctools #*** !doctools
#[call class::interface_caphandler.registry [method pkg_register] [arg pkg] [arg capname] [arg capdict] [arg fullcapabilitylist]] #[call class::interface_caphandler.registry [method pkg_register] [arg pkg] [arg capname] [arg capdict] [arg fullcapabilitylist]]
#handler may override and return 0 (indicating don't register)e.g if pkg capdict data wasn't valid #handler may override and return 0 (indicating don't register)e.g if pkg capdict data wasn't valid
#overridden handler must be able to handle multiple calls for same pkg - but it may return 1 or 0 as it wishes. #overridden handler must be able to handle multiple calls for same pkg - but it may return 1 or 0 as it wishes.
return 1 ;#default to permit return 1 ;#default to permit
} }
method pkg_unregister {pkg} { method pkg_unregister {pkg} {
#*** !doctools #*** !doctools
@ -106,9 +106,9 @@ tcl::namespace::eval punk::cap {
oo::class create ::punk::cap::class::interface_capprovider.registration { oo::class create ::punk::cap::class::interface_capprovider.registration {
#*** !doctools #*** !doctools
# [enum] CLASS [class interface_cappprovider.registration] # [enum] CLASS [class interface_cappprovider.registration]
# [para]Your provider package will need to instantiate this object under a sub-namespace called [namespace capsystem] within your package namespace. # [para]Your provider package will need to instantiate this object under a sub-namespace called [namespace capsystem] within your package namespace.
# [para]If your package namespace is mypackages::providerpkg then the object command would be at mypackages::providerpkg::capsystem::capprovider.registration # [para]If your package namespace is mypackages::providerpkg then the object command would be at mypackages::providerpkg::capsystem::capprovider.registration
# [para]Example code for your provider package to evaluate within its namespace: # [para]Example code for your provider package to evaluate within its namespace:
# [example { # [example {
#namespace eval capsystem { #namespace eval capsystem {
# if {[info commands capprovider.registration] eq ""} { # if {[info commands capprovider.registration] eq ""} {
@ -133,7 +133,7 @@ tcl::namespace::eval punk::cap {
#[para] This method must be overridden by your provider using oo::objdefine cappprovider.registration as in the example above. #[para] This method must be overridden by your provider using oo::objdefine cappprovider.registration as in the example above.
# There must be at least one 2-element list in the result for the provider to be registerable. # There must be at least one 2-element list in the result for the provider to be registerable.
#[para]The first element of the list is the capabilityname - which can be custom to your provider/handler packages - or a well-known name that other authors may use/implement. #[para]The first element of the list is the capabilityname - which can be custom to your provider/handler packages - or a well-known name that other authors may use/implement.
#[para]The second element is a dictionary of keys specific to the capability being implemented. It may be empty if the any potential capability handlers for the named capability don't require registration data. #[para]The second element is a dictionary of keys specific to the capability being implemented. It may be empty if the any potential capability handlers for the named capability don't require registration data.
error "interface_capprovider.registration not implemented by provider" error "interface_capprovider.registration not implemented by provider"
} }
#*** !doctools #*** !doctools
@ -142,11 +142,11 @@ tcl::namespace::eval punk::cap {
oo::class create ::punk::cap::class::interface_capprovider.provider { oo::class create ::punk::cap::class::interface_capprovider.provider {
#*** !doctools #*** !doctools
# [enum] CLASS [class interface_capprovider.provider] # [enum] CLASS [class interface_capprovider.provider]
# [para] Your provider package will need to instantiate this directly under it's own namespace with the command name of [emph {provider}] # [para] Your provider package will need to instantiate this directly under it's own namespace with the command name of [emph {provider}]
# [example { # [example {
# namespace eval mypackages::providerpkg { # namespace eval mypackages::providerpkg {
# punk::cap::class::interface_capprovider.provider create provider mypackages::providerpkg # punk::cap::class::interface_capprovider.provider create provider mypackages::providerpkg
# } # }
# }] # }]
# [list_begin definitions] # [list_begin definitions]
@ -229,7 +229,7 @@ tcl::namespace::eval punk::cap {
#Not all capability names have to be registered. #Not all capability names have to be registered.
#A package registering as a provider using register_package can include capabilitynames in it's capabilitylist which have no associated handler. #A package registering as a provider using register_package can include capabilitynames in it's capabilitylist which have no associated handler.
#such unregistered capabilitynames may be used just to flag something, or have datamembers significant to callers cooperatively interested in that capname. #such unregistered capabilitynames may be used just to flag something, or have datamembers significant to callers cooperatively interested in that capname.
#we allow registering a capability with an empty handler (capnamespace) - but this means another handler could be registered later. #we allow registering a capability with an empty handler (capnamespace) - but this means another handler could be registered later.
proc register_capabilityname {capname capnamespace} { proc register_capabilityname {capname capnamespace} {
#puts stderr "REGISTER_CAPABILITYNAME $capname $capnamespace" #puts stderr "REGISTER_CAPABILITYNAME $capname $capnamespace"
@ -243,10 +243,10 @@ tcl::namespace::eval punk::cap {
} }
} }
#allow register of existing capname iff there is no current handler #allow register of existing capname iff there is no current handler
#as handlers can be used to validate during provider registration - ideally handlers should be registered before any pkgs call register_package #as handlers can be used to validate during provider registration - ideally handlers should be registered before any pkgs call register_package
#we allow loading a handler later though - but will need to validate existing data from pkgs that have already registered as providers #we allow loading a handler later though - but will need to validate existing data from pkgs that have already registered as providers
if {[set hdlr [capability_get_handler $capname]] ne ""} { if {[set hdlr [capability_get_handler $capname]] ne ""} {
puts stderr "register_capabilityname cannot register capability:$capname with handler:$capnamespace. There is already a registered handler:$hdlr" puts stderr "register_capabilityname cannot register capability:$capname with handler:$capnamespace. There is already a registered handler:$hdlr"
return return
} }
#assertion: capnamespace may or may not be empty string, capname may or may not already exist in caps dict, caps $capname providers may have existing entries. #assertion: capnamespace may or may not be empty string, capname may or may not already exist in caps dict, caps $capname providers may have existing entries.
@ -295,14 +295,14 @@ tcl::namespace::eval punk::cap {
if {$count == 0} { if {$count == 0} {
set pkgposn [lsearch $providers $pkg] set pkgposn [lsearch $providers $pkg]
if {$pkgposn >= 0} { if {$pkgposn >= 0} {
set updated_providers [lreplace $providers $posn $posn] set updated_providers [lreplace $providers $posn $posn]
tcl::dict::set caps $capname providers $updated_providers tcl::dict::set caps $capname providers $updated_providers
} }
} }
} }
} }
} }
} }
proc capability_exists {capname} { proc capability_exists {capname} {
@ -328,7 +328,7 @@ tcl::namespace::eval punk::cap {
if {[tcl::dict::exists $caps $capname]} { if {[tcl::dict::exists $caps $capname]} {
return [tcl::dict::get $caps $capname handler] return [tcl::dict::get $caps $capname handler]
} }
return "" return ""
} }
proc call_handler {capname args} { proc call_handler {capname args} {
if {[set handler [capability_get_handler $capname]] eq ""} { if {[set handler [capability_get_handler $capname]] eq ""} {
@ -461,7 +461,7 @@ tcl::namespace::eval punk::cap {
#todo! #todo!
proc unregister_package {pkg {capname *}} { proc unregister_package {pkg {capname *}} {
variable pkgcapsdeclared variable pkgcapsdeclared
variable caps variable caps
if {[string match ::* $pkg]} { if {[string match ::* $pkg]} {
set pkg [string range $pkg 2 end] set pkg [string range $pkg 2 end]
@ -471,7 +471,7 @@ tcl::namespace::eval punk::cap {
set capabilitylist [dict get $pkgcapsdeclared $pkg] set capabilitylist [dict get $pkgcapsdeclared $pkg]
foreach c $capabilitylist { foreach c $capabilitylist {
set do_unregister 1 set do_unregister 1
lassign $c capname _capdict lassign $c capname _capdict
set cap_info [dict get $caps $capname] set cap_info [dict get $caps $capname]
set pkglist [dict get $cap_info providers] set pkglist [dict get $cap_info providers]
set posn [lsearch $pkglist $pkg] set posn [lsearch $pkglist $pkg]
@ -479,9 +479,9 @@ tcl::namespace::eval punk::cap {
if {[set capreg [punk::cap::capsystem::get_caphandler_registry $capname]] ne ""} { if {[set capreg [punk::cap::capsystem::get_caphandler_registry $capname]] ne ""} {
#review #review
# it seems not useful to allow the callback to block this unregister action # it seems not useful to allow the callback to block this unregister action
#the pkg may have multiple datasets for each capname so callback will only be called for first dataset we encounter #the pkg may have multiple datasets for each capname so callback will only be called for first dataset we encounter
#vetoing unregister would make this more complex for no particular advantage #vetoing unregister would make this more complex for no particular advantage
#if per dataset deregistration required this should probably be a separate thing #if per dataset deregistration required this should probably be a separate thing
$capreg pkg_unregister $pkg $capname $capreg pkg_unregister $pkg $capname
} }
set pkglist [lreplace $pkglist $posn $posn] set pkglist [lreplace $pkglist $posn $posn]
@ -510,7 +510,7 @@ tcl::namespace::eval punk::cap {
} }
} }
proc pkgcaps {} { proc pkgcaps {} {
variable pkgcapsdeclared variable pkgcapsdeclared
variable pkgcapsaccepted variable pkgcapsaccepted
set result [dict create] set result [dict create]
foreach {pkg capsdeclared} $pkgcapsdeclared { foreach {pkg capsdeclared} $pkgcapsdeclared {
@ -522,7 +522,7 @@ tcl::namespace::eval punk::cap {
dict set result $pkg accepted $accepted dict set result $pkg accepted $accepted
} }
return $result return $result
} }
proc capability {capname} { proc capability {capname} {
variable caps variable caps
@ -565,14 +565,14 @@ tcl::namespace::eval punk::cap {
#[subsection {Namespace punk::cap::advanced}] #[subsection {Namespace punk::cap::advanced}]
#[para] punk::cap::advanced API. Functions here are generally not the preferred way to interact with punk::cap. #[para] punk::cap::advanced API. Functions here are generally not the preferred way to interact with punk::cap.
#[para] In some cases they may allow interaction in less safe ways or may allow use of features that are unavailable in the base namespace. #[para] In some cases they may allow interaction in less safe ways or may allow use of features that are unavailable in the base namespace.
#[para] Some functions are here because they are only marginally or rarely useful, and they are here to keep the base API simple. #[para] Some functions are here because they are only marginally or rarely useful, and they are here to keep the base API simple.
#[list_begin definitions] #[list_begin definitions]
proc promote_provider {pkg} { proc promote_provider {pkg} {
#*** !doctools #*** !doctools
# [call advanced::[fun promote_provider] [arg pkg]] # [call advanced::[fun promote_provider] [arg pkg]]
#[para]Move the named provider package to the preferred end of the list (tail). #[para]Move the named provider package to the preferred end of the list (tail).
#[para]The active handler may or may not utilise this for preferencing. See documentation for the specific handler package to confirm. #[para]The active handler may or may not utilise this for preferencing. See documentation for the specific handler package to confirm.
#[para] #[para]
#[para] promote/demote doesn't always make a lot of sense .. should preferably be configurable per capapbility for multicap provider pkgs #[para] promote/demote doesn't always make a lot of sense .. should preferably be configurable per capapbility for multicap provider pkgs
#[para]The idea is to provide a crude way to preference/depreference packages independently of order the packages were loaded #[para]The idea is to provide a crude way to preference/depreference packages independently of order the packages were loaded
@ -615,7 +615,7 @@ tcl::namespace::eval punk::cap {
#*** !doctools #*** !doctools
# [call advanced::[fun demote_provider] [arg pkg]] # [call advanced::[fun demote_provider] [arg pkg]]
#[para]Move the named provider package to the preferred end of the list (tail). #[para]Move the named provider package to the preferred end of the list (tail).
#[para]The active handler may or may not utilise this for preferencing. See documentation for the specific handler package to confirm. #[para]The active handler may or may not utilise this for preferencing. See documentation for the specific handler package to confirm.
variable pkgcapsdeclared variable pkgcapsdeclared
variable caps variable caps
if {[string match ::* $pkg]} { if {[string match ::* $pkg]} {
@ -677,11 +677,11 @@ tcl::namespace::eval punk::cap {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::cap [namespace eval punk::cap { package provide punk::cap [namespace eval punk::cap {
variable version variable version
variable pkg punk::cap variable pkg punk::cap
set version 0.1.0 set version 0.1.0
variable README.md [string map [list %pkg% $pkg %ver% $version] { variable README.md [string map [list %pkg% $pkg %ver% $version] {
# punk capabilities system # punk capabilities system
## pkg: %pkg% version: %ver% ## pkg: %pkg% version: %ver%

4
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap/handlers/caphandler-0.1.0.tm vendored

@ -43,10 +43,10 @@ namespace eval punk::cap::handlers::caphandler {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::cap::handlers::caphandler [namespace eval punk::cap::handlers::caphandler { package provide punk::cap::handlers::caphandler [namespace eval punk::cap::handlers::caphandler {
variable pkg punk::cap::handlers::caphandler variable pkg punk::cap::handlers::caphandler
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

158
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm vendored

@ -23,7 +23,7 @@ package require punk::repo
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#register using: #register using:
# punk::cap::register_capabilityname templates ::punk::cap::handlers::templates # punk::cap::register_capabilityname templates ::punk::cap::handlers::templates
#By convention and for consistency, we don't register here during package loading - but require the calling app to do it. #By convention and for consistency, we don't register here during package loading - but require the calling app to do it.
# (even if it tends to be done immediately after package require anyway) # (even if it tends to be done immediately after package require anyway)
@ -67,11 +67,11 @@ namespace eval punk::cap::handlers::templates {
#for template pathtype module & shellproject* we can resolve whether it's within a project at registration time and store the projectbase rather than rechecking it each time the templates handler api is called #for template pathtype module & shellproject* we can resolve whether it's within a project at registration time and store the projectbase rather than rechecking it each time the templates handler api is called
#for template pathtype absolute - we can do the same. #for template pathtype absolute - we can do the same.
#There is a small chance for a long-running shell that a project is later created which makes the absolute path within a project - but it seems an unlikely case, and probably won't surprise the user that they need to relaunch the shell or reload the capsystem to see the change. #There is a small chance for a long-running shell that a project is later created which makes the absolute path within a project - but it seems an unlikely case, and probably won't surprise the user that they need to relaunch the shell or reload the capsystem to see the change.
#adhoc and currentproject* paths are relative to cwd - so no projectbase information can be stored at registration time. #adhoc and currentproject* paths are relative to cwd - so no projectbase information can be stored at registration time.
#not all template item types will need projectbase information - as the item data may be self-contained within the template structure - #not all template item types will need projectbase information - as the item data may be self-contained within the template structure -
#but project_layout will need it - or at least need to know if there is no project - because project_layout data is never stored in the template folder structure directly. #but project_layout will need it - or at least need to know if there is no project - because project_layout data is never stored in the template folder structure directly.
switch -- $pathtype { switch -- $pathtype {
adhoc { adhoc {
@ -95,7 +95,7 @@ namespace eval punk::cap::handlers::templates {
} else { } else {
set tm_exists [file exists $tmfile] set tm_exists [file exists $tmfile]
} }
if {![file exists $tmfile]} { if {!$tm_exists} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - unable to determine base folder for package '$pkg' which is attempting to register with punk::cap as a provider of '$capname' capability" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - unable to determine base folder for package '$pkg' which is attempting to register with punk::cap as a provider of '$capname' capability"
flush stderr flush stderr
return 0 return 0
@ -108,8 +108,10 @@ namespace eval punk::cap::handlers::templates {
#todo - handle wrapped or unwrapped tarjar files - in which case we have to adjust tmfolder appropriately #todo - handle wrapped or unwrapped tarjar files - in which case we have to adjust tmfolder appropriately
#set tpath [file normalize [file join $tmfile [dict get $capdict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder #set tpath [file normalize [file join $tmfile [dict get $capdict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder
set projectinfo [punk::repo::find_repos $tmfolder] #set projectinfo [punk::repo::find_repos $tmfolder] ;#slow - REVIEW
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $tmfolder]
#store the projectbase even if it's empty string #store the projectbase even if it's empty string
set extended_capdict $capdict set extended_capdict $capdict
set resolved_path [file join $tmfolder $path] set resolved_path [file join $tmfolder $path]
@ -128,7 +130,7 @@ namespace eval punk::cap::handlers::templates {
} }
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor ;#vendor key still required.. controlling vendor? dict set extended_capdict vendor $vendor ;#vendor key still required.. controlling vendor?
} }
currentproject { currentproject {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
@ -140,7 +142,7 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
} }
shellproject { shellproject {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
@ -148,9 +150,10 @@ namespace eval punk::cap::handlers::templates {
return 0 return 0
} }
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase] #set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
@ -166,11 +169,12 @@ namespace eval punk::cap::handlers::templates {
return 0 return 0
} }
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase] #set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} }
absolute { absolute {
@ -183,12 +187,13 @@ namespace eval punk::cap::handlers::templates {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' which doesn't seem to exist" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' which doesn't seem to exist"
return 0 return 0
} }
set projectinfo [punk::repo::find_repos $normpath] #set projectinfo [punk::repo::find_repos $normpath]
set projectbase [dict get $projectinfo closest] #set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $normpath]
#todo - verify no other provider has registered same absolute path - if sharing a project-external location is needed - they need their own subfolder #todo - verify no other provider has registered same absolute path - if sharing a project-external location is needed - they need their own subfolder
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict resolved_path $normpath dict set extended_capdict resolved_path $normpath
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} }
@ -199,7 +204,7 @@ namespace eval punk::cap::handlers::templates {
} }
# -- --- --- --- --- --- --- ---- --- # -- --- --- --- --- --- --- ---- ---
# update package internal data # update package internal data
# -- --- --- --- --- --- --- ---- --- # -- --- --- --- --- --- --- ---- ---
upvar ::punk::cap::handlers::templates::provider_info_$cname provider_info upvar ::punk::cap::handlers::templates::provider_info_$cname provider_info
@ -208,13 +213,13 @@ namespace eval punk::cap::handlers::templates {
} }
if {![info exists provider_info] || $extended_capdict ni [dict get $provider_info $pkg]} { if {![info exists provider_info] || $extended_capdict ni [dict get $provider_info $pkg]} {
#this checks for duplicates from the same provider - but not if other providers already added the path #this checks for duplicates from the same provider - but not if other providers already added the path
#review - #review -
dict lappend provider_info $pkg $extended_capdict dict lappend provider_info $pkg $extended_capdict
} }
# -- --- --- --- --- --- --- ---- --- # -- --- --- --- --- --- --- ---- ---
# instantiation of api at punk::cap::handlers::templates::api_$capname # instantiation of api at punk::cap::handlers::templates::api_$capname
# -- --- --- --- --- --- --- ---- --- # -- --- --- --- --- --- --- ---- ---
set apicmd "::punk::cap::handlers::templates::api_$capname" set apicmd "::punk::cap::handlers::templates::api_$capname"
if {[info commands $apicmd] eq ""} { if {[info commands $apicmd] eq ""} {
@ -227,12 +232,12 @@ namespace eval punk::cap::handlers::templates {
upvar ::punk::cap::handlers::templates::handled_caps hcaps upvar ::punk::cap::handlers::templates::handled_caps hcaps
foreach capname $hcaps { foreach capname $hcaps {
set cname [string map {. _} $capname] set cname [string map {. _} $capname]
upvar ::punk::cap::handlers::templates::provider_info_$cname my_provider_info upvar ::punk::cap::handlers::templates::provider_info_$cname my_provider_info
dict unset my_provider_info $pkg dict unset my_provider_info $pkg
#destroy api objects? #destroy api objects?
} }
} }
} }
} }
} }
@ -244,6 +249,18 @@ namespace eval punk::cap::handlers::templates {
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
namespace export * namespace export *
namespace eval class { namespace eval class {
variable PUNKARGS
#set argd [punk::args::get_dict {
# @id -id "::punk::cap::handlers::templates::class::api folders"
# -startdir -default ""
# @values -max 0
#} $args]
lappend PUNKARGS [list {
@id -id "::punk::cap::handlers::templates::class::api folders"
-startdir -default ""
@values -max 0
}]
oo::class create api { oo::class create api {
#return a dict keyed on folder with source pkg as value #return a dict keyed on folder with source pkg as value
constructor {capname} { constructor {capname} {
@ -253,10 +270,8 @@ namespace eval punk::cap::handlers::templates {
set capabilityname $capname set capabilityname $capname
} }
method folders {args} { method folders {args} {
set argd [punk::args::get_dict { #puts "--folders $args"
-startdir -default "" set argd [punk::args::parse $args withid "::punk::cap::handlers::templates::class::api folders"]
*values -max 0
} $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set opt_startdir [dict get $opts -startdir] set opt_startdir [dict get $opts -startdir]
@ -269,6 +284,10 @@ namespace eval punk::cap::handlers::templates {
set startdir $opt_startdir set startdir $opt_startdir
} }
} }
set searchbase $startdir
#set pathinfo [punk::repo::find_repos $searchbase] ;#relatively slow! REVIEW - pass as arg? cache?
#set pwd_projectroot [dict get $pathinfo closest]
set pwd_projectroot [punk::repo::find_project $searchbase]
variable capabilityname variable capabilityname
@ -292,7 +311,7 @@ namespace eval punk::cap::handlers::templates {
set found_paths_absolute [list] set found_paths_absolute [list]
foreach pkg $providerpkg { foreach pkg $providerpkg {
set found_paths [list] set found_paths [list]
#set acceptedlist [dict get [punk::cap::pkgcap $pkg $capabilityname] accepted] #set acceptedlist [dict get [punk::cap::pkgcap $pkg $capabilityname] accepted]
@ -313,13 +332,13 @@ namespace eval punk::cap::handlers::templates {
set module_projectroot [dict get $capdecl_extended projectbase] set module_projectroot [dict get $capdecl_extended projectbase]
dict lappend found_paths_module $vendor [list pkg $pkg path [dict get $capdecl_extended resolved_path] pathtype $pathtype projectbase $module_projectroot] dict lappend found_paths_module $vendor [list pkg $pkg path [dict get $capdecl_extended resolved_path] pathtype $pathtype projectbase $module_projectroot]
} elseif {$pathtype eq "currentproject_multivendor"} { } elseif {$pathtype eq "currentproject_multivendor"} {
set searchbase $startdir #set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase] #set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest] #set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} { if {$pwd_projectroot ne ""} {
set deckbase [file join $pwd_projectroot $path] set deckbase [file join $pwd_projectroot $path]
if {![file exists $deckbase]} { if {![file exists $deckbase]} {
continue continue
} }
#add vendor/x folders first - earlier in list is lower priority #add vendor/x folders first - earlier in list is lower priority
set vendorbase [file join $deckbase vendor] set vendorbase [file join $deckbase vendor]
@ -348,9 +367,9 @@ namespace eval punk::cap::handlers::templates {
} }
} }
} elseif {$pathtype eq "currentproject"} { } elseif {$pathtype eq "currentproject"} {
set searchbase $startdir #set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase] #set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest] #set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} { if {$pwd_projectroot ne ""} {
#path relative to projectroot already validated by handler as being within a currentproject_multivendor tree #path relative to projectroot already validated by handler as being within a currentproject_multivendor tree
set targetfolder [file join $pwd_projectroot $path] set targetfolder [file join $pwd_projectroot $path]
@ -368,7 +387,7 @@ namespace eval punk::cap::handlers::templates {
if {$shell_projectroot ne ""} { if {$shell_projectroot ne ""} {
set deckbase [file join $shell_projectroot $path] set deckbase [file join $shell_projectroot $path]
if {![file exists $deckbase]} { if {![file exists $deckbase]} {
continue continue
} }
#add vendor/x folders first - earlier in list is lower priority #add vendor/x folders first - earlier in list is lower priority
set vendorbase [file join $deckbase vendor] set vendorbase [file join $deckbase vendor]
@ -470,25 +489,27 @@ namespace eval punk::cap::handlers::templates {
return $folderdict return $folderdict
} }
method get_itemdict_projectlayouts {args} { method get_itemdict_projectlayouts {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
*opts -anyopts 1 @id -id "::punk::cap::handlers::templates::class::api get_itemdict_projectlayouts"
@opts -anyopts 1
#peek -startdir while allowing all other opts/vals to be verified down-the-line instead of here #peek -startdir while allowing all other opts/vals to be verified down-the-line instead of here
-startdir -default "" -startdir -default ""
*values -maxvalues -1 @values -maxvalues -1
} $args] } $args]
set opt_startdir [dict get $argd opts -startdir] set opt_startdir [dict get $argd opts -startdir]
if {$opt_startdir eq ""} { if {$opt_startdir eq ""} {
set searchbase [pwd] set searchbase [pwd]
} else { } else {
set searchbase $opt_startdir set searchbase $opt_startdir
} }
set refdict [my get_itemdict_projectlayoutrefs {*}$args] set refdict [my get_itemdict_projectlayoutrefs {*}$args]
set layoutdict [dict create] set layoutdict [dict create]
set projectinfo [punk::repo::find_repos $searchbase] #set projectinfo [punk::repo::find_repos $searchbase]
set projectroot [dict get $projectinfo closest] #set projectroot [dict get $projectinfo closest]
set projectroot [punk::repo::find_project $searchbase]
dict for {layoutname refinfo} $refdict { dict for {layoutname refinfo} $refdict {
set templatepathtype [dict get $refinfo sourceinfo pathtype] set templatepathtype [dict get $refinfo sourceinfo pathtype]
@ -500,16 +521,18 @@ namespace eval punk::cap::handlers::templates {
# e.g ref may be @vendor+punks+othersample@sample-0.1 or layoutalias-1.1@vendor+punk+othersample@sample-0.1 # e.g ref may be @vendor+punks+othersample@sample-0.1 or layoutalias-1.1@vendor+punk+othersample@sample-0.1
#there must always be an @ before vendor or custom . There is either a template-name alias or empty string before this first @ #there must always be an @ before vendor or custom . There is either a template-name alias or empty string before this first @
#trim off first @ part #trim off first @ part
set tailats [join [lrange $atparts 1 end] @] set tailats [join [lrange $atparts 1 end] @]
# @ parts after the first are part of the path within the project_layouts structure # @ parts after the first are part of the path within the project_layouts structure
set subpathlist [split $tailats +] set subpathlist [split $tailats +]
if {[dict exists $refinfo sourceinfo projectbase]} { if {[dict exists $refinfo sourceinfo projectbase]} {
#some template pathtypes refer to the projectroot from the template - not the cwd #some template pathtypes refer to the projectroot from the template - not the cwd
set projectroot [dict get $refinfo sourceinfo projectbase] set ref_projectroot [dict get $refinfo sourceinfo projectbase]
} else {
set ref_projectroot $projectroot
} }
if {$projectroot ne ""} { if {$ref_projectroot ne ""} {
set layoutroot [file join $projectroot src/project_layouts] set layoutroot [file join $ref_projectroot src/project_layouts]
set layoutfolder [file join $layoutroot {*}$subpathlist] set layoutfolder [file join $layoutroot {*}$subpathlist]
if {[file isdirectory $layoutfolder]} { if {[file isdirectory $layoutfolder]} {
#todo - check if layoutname already in layoutdict append .ref path to list of refs that linked to this layout? #todo - check if layoutname already in layoutdict append .ref path to list of refs that linked to this layout?
@ -549,7 +572,7 @@ namespace eval punk::cap::handlers::templates {
if {$vendor ne "_project"} { if {$vendor ne "_project"} {
set itemname $vendor.$itemname set itemname $vendor.$itemname
} }
return $itemname return $itemname
}}} }}}
} }
set arglist [concat $config $args] set arglist [concat $config $args]
@ -619,7 +642,7 @@ namespace eval punk::cap::handlers::templates {
}}}\ }}}\
-command_get_item_name {apply {{vendor basefolder itempath} { -command_get_item_name {apply {{vendor basefolder itempath} {
set relativepath [punk::path::relative $basefolder $itempath] set relativepath [punk::path::relative $basefolder $itempath]
set dirs [file dirname $relativepath] set dirs [file dirname $relativepath]
if {$dirs eq "."} { if {$dirs eq "."} {
set dirs "" set dirs ""
@ -632,7 +655,7 @@ namespace eval punk::cap::handlers::templates {
} }
if {$vendor ne "_project"} { if {$vendor ne "_project"} {
set tname ${vendor}.$tname set tname ${vendor}.$tname
} }
return $tname return $tname
}}} }}}
} }
@ -641,19 +664,20 @@ namespace eval punk::cap::handlers::templates {
} }
#shared algorithm for get_itemdict_* methods #shared algorithm for get_itemdict_* methods
#requires a -templatefolder_subdir indicating a directory within each template base folder in which to search #requires a -templatefolder_subdir indicating a directory within each template base folder in which to search
#and a file selection mechanism command -command_get_items_from_base #and a file selection mechanism command -command_get_items_from_base
#and a name determining command -command_get_item_name #and a name determining command -command_get_item_name
method _get_itemdict {args} { method _get_itemdict {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
*proc -name _get_itemdict @id -id "::punk::cap::handlers::templates::class::api _get_itemdict"
*opts -anyopts 0 @cmd -name _get_itemdict
@opts -anyopts 0
-startdir -default "" -startdir -default ""
-templatefolder_subdir -optional 0 -templatefolder_subdir -optional 0
-command_get_items_from_base -optional 0 -command_get_items_from_base -optional 0
-command_get_item_name -optional 0 -command_get_item_name -optional 0
-not -default "" -multiple 1 -not -default "" -multiple 1
*values -maxvalues -1 @values -maxvalues -1
globsearches -default * -multiple 1 globsearches -default * -multiple 1
} $args] } $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -692,12 +716,12 @@ namespace eval punk::cap::handlers::templates {
set items_here [dict create] ;#maintain a list keyed on name for sorting within this base only set items_here [dict create] ;#maintain a list keyed on name for sorting within this base only
foreach itempath $matches { foreach itempath $matches {
set itemname [{*}$opt_command_get_item_name $vendor $basefolder $itempath] set itemname [{*}$opt_command_get_item_name $vendor $basefolder $itempath]
dict set items_here $itemname [list item $itempath baseinfo $baseinfo] dict set items_here $itemname [list item $itempath baseinfo $baseinfo]
#lappend items [list item $itempath baseinfo $baseinfo] #lappend items [list item $itempath baseinfo $baseinfo]
} }
set ordered_names [lsort [dict keys $items_here]] set ordered_names [lsort [dict keys $items_here]]
#add to the outer items list #add to the outer items list
foreach nm $ordered_names { foreach nm $ordered_names {
set iteminfo [dict get $items_here $nm] set iteminfo [dict get $items_here $nm]
lappend items [list originalname $nm iteminfo $iteminfo] lappend items [list originalname $nm iteminfo $iteminfo]
} }
@ -710,8 +734,8 @@ namespace eval punk::cap::handlers::templates {
set itempath [dict get $iteminfo item] set itempath [dict get $iteminfo item]
set baseinfo [dict get $iteminfo baseinfo] set baseinfo [dict get $iteminfo baseinfo]
if {![dict exists $seen_dict $oname]} { if {![dict exists $seen_dict $oname]} {
dict set seen_dict $oname 1 dict set seen_dict $oname 1
dict set itemdict $oname [list path $itempath {*}$baseinfo] ; #first seen of oname gets no number dict set itemdict $oname [list path $itempath {*}$baseinfo] ; #first seen of oname gets no number
} else { } else {
set n [dict get $seen_dict $oname] set n [dict get $seen_dict $oname]
incr n incr n
@ -725,7 +749,7 @@ namespace eval punk::cap::handlers::templates {
set result [dict create] set result [dict create]
set keys [lreverse [dict keys $itemdict]] set keys [lreverse [dict keys $itemdict]]
foreach k $keys { foreach k $keys {
set maybe "" set maybe ""
foreach g $globsearches { foreach g $globsearches {
if {[string match $g $k]} { if {[string match $g $k]} {
set maybe $k set maybe $k
@ -740,7 +764,7 @@ namespace eval punk::cap::handlers::templates {
break break
} }
} }
} }
if {$maybe ne "" && $not eq ""} { if {$maybe ne "" && $not eq ""} {
dict set result $k [dict get $itemdict $k] dict set result $k [dict get $itemdict $k]
} }
@ -755,12 +779,16 @@ namespace eval punk::cap::handlers::templates {
} }
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::cap::handlers::templates ::punk::cap::handlers::templates::class
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::cap::handlers::templates [namespace eval punk::cap::handlers::templates { package provide punk::cap::handlers::templates [namespace eval punk::cap::handlers::templates {
variable pkg punk::cap::handlers::templates variable pkg punk::cap::handlers::templates
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

591
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm vendored

File diff suppressed because it is too large Load Diff

487
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/config-0.1.tm vendored

@ -0,0 +1,487 @@
tcl::namespace::eval punk::config {
variable loaded
variable startup ;#include env overrides
variable running
variable punk_env_vars
variable other_env_vars
variable vars
namespace export {[a-z]*}
#todo - XDG_DATA_HOME etc
#https://specifications.freedesktop.org/basedir-spec/latest/
# see also: http://hiphish.github.io/blog/2020/08/30/dotfiles-were-a-mistake/
proc init {} {
variable defaults
variable startup
variable running
variable punk_env_vars
variable punk_env_vars_config
variable other_env_vars
variable other_env_vars_config
set exename ""
catch {
#catch for safe interps
#safe base will return empty string, ordinary safe interp will raise error
set exename [tcl::info::nameofexecutable]
}
if {$exename ne ""} {
set exefolder [file dirname $exename]
#default file logs to logs folder at same level as exe if writable, or empty string
set log_folder [file normalize $exefolder/../logs] ;#~2ms
#tcl::dict::set startup scriptlib $exefolder/scriptlib
#tcl::dict::set startup apps $exefolder/../../punkapps
#todo - use punk main.tcl location instead - exefolder doesn't work if system tclsh used etc
set default_scriptlib $exefolder/scriptlib
set default_apps $exefolder/../../punkapps
if {[file isdirectory $log_folder] && [file writable $log_folder]} {
#tcl::dict::set startup logfile_stdout $log_folder/repl-exec-stdout.txt
#tcl::dict::set startup logfile_stderr $log_folder/repl-exec-stderr.txt
set default_logfile_stdout $log_folder/repl-exec-stdout.txt
set default_logfile_stderr $log_folder/repl-exec-stderr.txt
} else {
set default_logfile_stdout ""
set default_logfile_stderr ""
}
} else {
#probably a safe interp - which cannot access info nameofexecutable even if access given to the location via punk::island
#review - todo?
#tcl::dict::set startup scriptlib ""
#tcl::dict::set startup apps ""
set default_scriptlib ""
set default_apps ""
set default_logfile_stdout ""
set default_logfile_stderr ""
}
# auto_exec_mechanism ;#whether to use exec instead of experimental shellfilter::run
#optional channel transforms on stdout/stderr.
#can sometimes be useful to distinguish eventloop stdout/stderr writes compared to those triggered directly from repl commands
#If no distinction necessary - should use default_color_<chan>
#The counterpart: default_color_<chan>_repl is a transform that is added and removed with each repl evaluation.
#startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default
set default_color_stdout brightwhite ;#stdout colour including background calls (after etc)
set default_color_stdout_repl "" ;#stdout colour applied during direct repl call only
#This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful.
#set default_color_stderr "red bold"
#set default_color_stderr "web-lightsalmon"
set default_color_stderr yellow ;#limit to basic colours for wider terminal support. yellow = term-olive
set default_color_stderr_repl "" ;#during repl call only
set homedir ""
if {[catch {
#depending on which build of tcl - some safe interps prior to bugfix https://core.tcl-lang.org/tcl/info/3aa487993f will return a homedir value in an unmodified safe interp
#other 'safe' interps may have explicitly made this available - we shouldn't override that decision here using interp issafe so we can't compensate for versions which shouldn't really be returning this in the safe interp
set homedir [file home]
} errM]} {
#tcl 8.6 doesn't have file home.. try again
if {[info exists ::env(HOME)]} {
set homedir $::env(HOME)
}
}
# per user xdg vars
# ---
set default_xdg_config_home "" ;#config data - portable
set default_xdg_data_home "" ;#data the user likely to want to be portable
set default_xdg_cache_home "" ;#local cache
set default_xdg_state_home "" ;#persistent user data such as logs, but not as important or as portable as those in xdg_data_home
# ---
set default_xdg_data_dirs "" ;#non-user specific
#xdg_config_dirs ?
#xdg_runtime_dir ?
#review. we are assuming if we can't get a home dir - then all the xdg vars including xdg_data_dirs aren't likely to be useful (as presumably filesystem access is absent)
#(safe interp generally won't have access to ::env either)
#This coupling doesn't necessarily hold - its possible the relevant env vars were copied to a safe interp - although that would be a policy that would make disabling 'info home' inconsistent.
if {$homedir ne ""} {
if {"windows" eq $::tcl_platform(platform)} {
#as much as I'd prefer to use ~/.local/share and ~/.config to keep them more consistent with unixlike platforms - the vast majority of apps put them where microsoft wants them.
#we have a choice of LOCALAPPDATA vs APPDATA (local to machine vs potentially roaming/redirected in a corporate environment)
#using the roaming location should not impact users who aren't using a domain controller but is potentially much more convenient for those who do.
if {[info exists ::env(APPDATA)]} {
set default_xdg_config_home $::env(APPDATA)
set default_xdg_data_home $::env(APPDATA)
}
#The xdg_cache_home should be kept local
if {[info exists ::env(LOCALAPPDATA)]} {
set default_xdg_cache_home $::env(LOCALAPPDATA)
set default_xdg_state_home $::env(LOCALAPPDATA)
}
if {[info exists ::env(PROGRAMDATA)]} {
#- equiv env(ALLUSERSPROFILE) ?
set default_xdg_data_dirs $::env(PROGRAMDATA)
}
} else {
#follow defaults as specified on freedesktop.org e.g https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
set default_xdg_config_home [file join $homedir .config]
set default_xdg_data_home [file join $homedir .local share]
set default_xdg_cache_home [file join $homedir .cache]
set default_xdg_state_home [file join $homedir .local state]
set default_xdg_data_dirs /usr/local/share
}
}
set defaults [dict create\
apps $default_apps\
config ""\
configset ".punkshell"\
scriptlib $default_scriptlib\
color_stdout $default_color_stdout\
color_stdout_repl $default_color_stdout_repl\
color_stderr $default_color_stderr\
color_stderr_repl $default_color_stderr_repl\
logfile_stdout $default_logfile_stdout\
logfile_stderr $default_logfile_stderr\
logfile_active 0\
syslog_stdout "127.0.0.1:514"\
syslog_stderr "127.0.0.1:514"\
syslog_active 0\
auto_exec_mechanism exec\
auto_noexec 0\
xdg_config_home $default_xdg_config_home\
xdg_data_home $default_xdg_data_home\
xdg_cache_home $default_xdg_cache_home\
xdg_state_home $default_xdg_state_home\
xdg_data_dirs $default_xdg_data_dirs\
theme_posh_override ""\
posh_theme ""\
posh_themes_path ""\
]
set startup $defaults
#load values from saved config file - $xdg_config_home/punk/punk.config ?
#typically we want env vars to override the stored config - as env vars conventionally used on some commandlines.
#that's possibly ok for the PUNK_ vars
#however.. others like the xdg vars and NOCOLOR may apply to other apps.. and we may want to override them from the saved config?
#making some env vars override saved config values and some not would be potentially confusing. may need one/more specific settings or env vars to determine which takes precedence?
#simpler is probably just to let env vars take precedence - and warn when saving or viewing config that the saved values are being overridden
#- requiring user to manually unset any unwanted env vars when launching?
#we are likely to want the saved configs for subshells/decks to override them however.
#todo - load/save config file
#todo - define which configvars are settable in env
#list of varname varinfo where varinfo is a sub dictionary (type key is mandatory, with value from: string,pathlist,boolean)
set punk_env_vars_config [dict create \
PUNK_APPS {type pathlist}\
PUNK_CONFIG {type string}\
PUNK_CONFIGSET {type string}\
PUNK_SCRIPTLIB {type string}\
PUNK_AUTO_EXEC_MECHANISM {type string}\
PUNK_AUTO_NOEXEC {type string default 0 help "set 1 to set Tcl's ::auto_noexec true.\nStops 'unknown' from running external programs"}\
PUNK_COLOR_STDERR {type string help "stderr colour transform. Use 'punk::ansi::a?' to see colour names"}\
PUNK_COLOR_STDERR_REPL {type string help "stderr colour transform only while command running (not active during 'after')"}\
PUNK_COLOR_STDOUT {type string help "stdout colour transform. Use 'punk::ansi::a?' to see colour names"}\
PUNK_COLOR_STDOUT_REPL {type string help "stdout colour transform only while command running (not active during 'after')"}\
PUNK_LOGFILE_STDOUT {type string}\
PUNK_LOGFILE_STDERR {type string}\
PUNK_LOGFILE_ACTIVE {type string}\
PUNK_SYSLOG_STDOUT {type string}\
PUNK_SYSLOG_STDERR {type string}\
PUNK_SYSLOG_ACTIVE {type string}\
PUNK_THEME_POSH_OVERRIDE {type string}\
]
set punk_env_vars [dict keys $punk_env_vars_config]
#override with env vars if set
foreach {evar varinfo} $punk_env_vars_config {
if {[info exists ::env($evar)]} {
set vartype [dict get $varinfo type]
set f [set ::env($evar)]
if {$f ne "default"} {
#e.g PUNK_SCRIPTLIB -> scriptlib
set varname [tcl::string::tolower [tcl::string::range $evar 5 end]]
if {$vartype eq "pathlist"} {
#colon vs semicolon path sep is problematic for windows environments where unix-like systems such as cygwin/wsl are used and a variable may be set for either the native path separator or the unix-like system
#Even without the colon vs semicolon issue, native vs unix-like paths on windows mixed environment systems can cause grief.
#For now at least, we will simply respect the platform pathSeparator and hope the user manages the environment variables appropriately.
#some programs do automatic translation - which is a nice idea in principle - but is also prone to error as we don't know if it's already occurred or not depending on how things are launched.
#An example of where this sort of thing can go wrong is env(TCLLIBPATH) - which is defined as a space separated list not requiring further splitting
# - but some programs have been known to split this value on colon anyway, which breaks things on windows.
set paths [split $f $::tcl_platform(pathSeparator)]
set final [list]
#eliminate empty values (leading or trailing or extraneous separators)
foreach p $paths {
if {[tcl::string::trim $p] ne ""} {
lappend final $p
}
}
tcl::dict::set startup $varname $final
} else {
tcl::dict::set startup $varname $f
}
}
}
}
# https://no-color.org
#if {[info exists ::env(NO_COLOR)]} {
# if {$::env(NO_COLOR) ne ""} {
# set colour_disabled 1
# }
#}
set other_env_vars_config [dict create\
NO_COLOR {type string}\
XDG_CONFIG_HOME {type string}\
XDG_DATA_HOME {type string}\
XDG_CACHE_HOME {type string}\
XDG_STATE_HOME {type string}\
XDG_DATA_DIRS {type pathlist}\
POSH_THEME {type string}\
POSH_THEMES_PATH {type string}\
TCLLIBPATH {type string}\
]
lassign [split [info tclversion] .] tclmajorv tclminorv
#don't rely on lseq or punk::lib for now..
set relevant_minors [list]
for {set i 0} {$i <= $tclminorv} {incr i} {
lappend relevant_minors $i
}
foreach minor $relevant_minors {
set vname TCL${tclmajorv}_${minor}_TM_PATH
if {$minor eq $tclminorv || [info exists ::env($vname)]} {
dict set other_env_vars_config $vname {type string}
}
}
set other_env_vars [dict keys $other_env_vars_config]
foreach {evar varinfo} $other_env_vars_config {
if {[info exists ::env($evar)]} {
set vartype [dict get $varinfo type]
set f [set ::env($evar)]
if {$f ne "default"} {
set varname [tcl::string::tolower $evar]
if {$vartype eq "pathlist"} {
set paths [split $f $::tcl_platform(pathSeparator)]
set final [list]
#eliminate empty values (leading or trailing or extraneous separators)
foreach p $paths {
if {[tcl::string::trim $p] ne ""} {
lappend final $p
}
}
tcl::dict::set startup $varname $final
} else {
tcl::dict::set startup $varname $f
}
}
}
}
#unset -nocomplain vars
#todo
set running [tcl::dict::create]
set running [tcl::dict::merge $running $startup]
}
init
#todo
proc Apply {config} {
puts stderr "punk::config::Apply partially implemented"
set configname [string map {-config ""} $config]
if {$configname in {startup running}} {
upvar ::punk::config::$configname applyconfig
if {[dict exists $applyconfig auto_noexec]} {
set auto [dict get $applyconfig auto_noexec]
if {![string is boolean -strict $auto]} {
error "config::Apply error - invalid data for auto_noexec:'$auto' - expected boolean"
}
if {$auto} {
set ::auto_noexec 1
} else {
#puts "auto_noexec false"
unset -nocomplain ::auto_noexec
}
}
} else {
error "no config named '$config' found"
}
return "apply done"
}
Apply startup
#todo - consider how to divide up settings, categories, 'devices', decks etc
proc get_running_global {varname} {
variable running
if {[dict exists $running $varname]} {
return [dict get $running $varname]
}
error "No such global configuration item '$varname' found in running config"
}
proc get_startup_global {varname} {
variable startup
if {[dict exists $startup $varname]} {
return [dict get $startup $varname]
}
error "No such global configuration item '$varname' found in startup config"
}
proc get {whichconfig {globfor *}} {
variable startup
variable running
switch -- $whichconfig {
config - startup - startup-config - startup-configuration {
#show *startup* config - different behaviour may be confusing to those used to router startup and running configs
set configdata $startup
}
running - running-config - running-configuration {
set configdata $running
}
default {
error "Unknown config name '$whichconfig' - try startup or running"
}
}
if {$globfor eq "*"} {
return $configdata
} else {
set keys [dict keys $configdata [string tolower $globfor]]
set filtered [dict create]
foreach k $keys {
dict set filtered $k [dict get $configdata $k]
}
return $filtered
}
}
proc configure {args} {
set argdef {
@id -id ::punk::config::configure
@cmd -name punk::config::configure -help\
"UNIMPLEMENTED"
@values -min 1 -max 1
whichconfig -type string -choices {startup running stop}
}
set argd [punk::args::get_dict $argdef $args]
return "unimplemented - $argd"
}
proc show {whichconfig {globfor *}} {
#todo - tables for console
set configdata [punk::config::get $whichconfig $globfor]
return [punk::lib::showdict $configdata]
}
#e.g
# copy running-config startup-config
# copy startup-config test-config.cfg
# copy backup-config.cfg running-config
#review - consider the merge vs overwrite feature of some routers.. where copy to running-config does a merge rather than an overwrite
#This is to allow partial configs to be loaded to running, whereas a save of running to any target is always a complete configuration
proc copy {args} {
set argdef {
@id -id ::punk::config::copy
@cmd -name punk::config::copy -help\
"Copy a partial or full configuration from one config to another
If a target config has additional settings, then the source config can be considered to be partial with regards to the target.
"
-type -default "" -choices {replace merge} -help\
"Defaults to merge when target is running-config
Defaults to replace when source is running-config"
@values -min 2 -max 2
fromconfig -help\
"running or startup or file name (not fully implemented)"
toconfig -help\
"running or startup or file name (not fully implemented)"
}
set argd [punk::args::get_dict $argdef $args]
set fromconfig [dict get $argd values fromconfig]
set toconfig [dict get $argd values toconfig]
set fromconfig [string map {-config ""} $fromconfig]
set toconfig [string map {-config ""} $toconfig]
set copytype [dict get $argd opts -type]
#todo - warn & prompt if doing merge copy to startup
switch -exact -- $fromconfig-$toconfig {
running-startup {
if {$copytype eq ""} {
set copytype replace ;#full configuration
}
if {$copytype eq "replace"} {
error "punk::config::copy error. full configuration copy from running to startup config not yet supported"
} else {
error "punk::config::copy error. merge configuration copy from running to startup config not yet supported"
}
}
startup-running {
#default type merge - even though it's not always what is desired
if {$copytype eq ""} {
set copytype merge ;#load in a partial configuration
}
#warn/prompt either way
if {$copytype eq "replace"} {
#some routers require use of a separate command for this branch.
#presumably to ensure the user doesn't accidentally load partials onto a running system
#
error "punk::config::copy error. full configuration copy from startup to overwrite running config not supported"
} else {
error "punk::config::copy error. merge copy from possibly partial configuration: startup to running config not currently supported"
}
}
default {
error "punk::config::copy error. copy must from running to startup or startup to running. File sources/targets not yet supported"
}
}
}
}
#todo - move to cli?
::tcl::namespace::eval punk::config {
#todo - something better - 'previous' rather than reverting to startup
proc channelcolors {{onoff {}}} {
variable running
variable startup
if {![string length $onoff]} {
return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]]
} else {
if {![string is boolean $onoff]} {
error "channelcolors: invalid value $onoff - expected boolean: true|false|on|off|1|0|yes|no"
}
if {$onoff} {
dict set running color_stdout [dict get $startup color_stdout]
dict set running color_stderr [dict get $startup color_stderr]
} else {
dict set running color_stdout ""
dict set running color_stderr ""
}
}
return [list stdout [dict get $running color_stdout] stderr [dict get $running color_stderr]]
}
}
package provide punk::config [tcl::namespace::eval punk::config {
variable version
set version 0.1
}]

1586
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm vendored

File diff suppressed because it is too large Load Diff

1
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/docgen-0.1.0.tm vendored

@ -31,6 +31,7 @@ namespace eval punk::docgen {
error "get_doctools_comments file '$fname' not found" error "get_doctools_comments file '$fname' not found"
} }
set fd [open $fname r] set fd [open $fname r]
chan conf $fd -translation binary
set data [read $fd] set data [read $fd]
close $fd close $fd
if {![string match "*#\**!doctools*" $data]} { if {![string match "*#\**!doctools*" $data]} {

403
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/du-0.1.0.tm vendored

@ -19,6 +19,7 @@
##e.g package require frobz ##e.g package require frobz
package require punk::mix::base package require punk::mix::base
package require struct::set package require struct::set
package require punk::args
namespace eval punk::du { namespace eval punk::du {
@ -486,29 +487,158 @@ namespace eval punk::du {
return [lindex [lappend winfile_attributes $bitmask [twapi::decode_file_attributes $bitmask]] end] return [lindex [lappend winfile_attributes $bitmask [twapi::decode_file_attributes $bitmask]] end]
} }
} }
proc attributes_twapi {path {detail basic}} { variable win_reparse_tags
#implied prefix for all names IO_REPARSE_TAG_
#list of reparse tags: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4
set win_reparse_tags [dict create\
RESERVED_ZERO [list hex 0x00000000 obsolete 0 serverside 0 meaning "Reserved reparse tag value"]\
RESERVED_ONE [list hex 0x00000001 obsolete 0 serverside 0 meaning "Reserved reparse tag value"]\
RESERVED_TWO [list hex 0x00000002 obsolete 0 serverside 0 meaning "Reserved reparse tag value"]\
MOUNT_POINT [list hex 0xA0000003 obsolete 0 serverside 0 meaning "Used for mount point support"]\
HSM [list hex 0xC0000004 obsolete 1 serverside 0 meaning "Obsolete. Used by legacy Hierarchical Storage Manager Product"]\
DRIVE_EXTENDER [list hex 0x80000005 obsolete 0 serverside 0 meaning "Home server drive extender"]\
HSM2 [list hex 0xC0000006 obsolete 1 serverside 0 meaning "Obsolete. Used by legacy Hierarchical Storage Manager Product"]\
SIS [list hex 0x80000007 obsolete 0 serverside 1 meaning "Used by single-instance storage (SIS) filter driver."]\
WIM [list hex 0x80000008 obsolete 0 serverside 1 meaning "Used by the WIM Mount filter."]\
CSV [list hex 0x80000008 obsolete 1 serverside 1 meaning "Obsolete. Used by Clustered Shared Volumes (CSV) version 1 in Windows Server 2008 R2 operating system. "]\
DFS [list hex 0x8000000A obsolete 0 serverside 1 meaning "Used by the DFS filter. The DFS is described in the Distributed File System (DFS): Referral Protocol Specification \[MS-DFSC\]."]\
FILTER_MANAGER [list hex 0x8000000B obsolete 0 serverside 0 meaning "Used by filter manager test harness"]\
SYMLINK [list hex 0xA000000C obsolete 0 serverside 0 meaning "Used for symbolic link support."]\
IIS_CACHE [list hex 0xA0000010 obsolete 0 serverside 1 meaning "Used by Microsoft Internet Information Services (IIS) caching. "]\
DFSR [list hex 0x80000012 obsolete 0 serverside 1 meaning "Used by the DFS filter. The DFS is described in \[MS-DFSC\]. "]\
DEDUP [list hex 0x80000013 obsolete 0 serverside 1 meaning "Used by the Data Deduplication (Dedup) filter. "]\
APPXSTRM [list hex 0xC0000014 obsolete 0 serverside 0 meaning "Not used."]\
NFS [list hex 0x80000014 obsolete 0 serverside 1 meaning "Used by the Network File System (NFS) component. "]\
FILE_PLACEHOLDER [list hex 0x80000015 obsolete 1 serverside 1 meaning "Obsolete. Used by Windows Shell for legacy placeholder files in Windows 8.1. "]\
DFM [list hex 0x80000016 obsolete 0 serverside 1 meaning "Used by the Dynamic File filter. "]\
WOF [list hex 0x80000017 obsolete 0 serverside 1 meaning "Used by the Windows Overlay filter, for either WIMBoot or single-file compression."]\
WCI [list hex 0x80000018 obsolete 0 serverside 1 meaning "Used by the Windows Container Isolation filter. "]\
WCI_2 [list hex 0x90001018 obsolete 0 serverside 1 meaning "Used by the Windows Container Isolation filter. "]\
GLOBAL_REPARSE [list hex 0xA0000019 obsolete 0 serverside 1 meaning "Used by NPFS to indicate a named pipe symbolic link from a server silo into the host silo."]\
CLOUD [list hex 0x9000001A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_1 [list hex 0x9000101A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_2 [list hex 0x9000201A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_3 [list hex 0x9000301A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_4 [list hex 0x9000401A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_5 [list hex 0x9000501A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_6 [list hex 0x9000601A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_7 [list hex 0x9000701A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_8 [list hex 0x9000801A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_9 [list hex 0x9000901A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_A [list hex 0x9000A01A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_B [list hex 0x9000B01A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_C [list hex 0x9000C01A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_D [list hex 0x9000D01A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_E [list hex 0x9000E01A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
CLOUD_F [list hex 0x9000F01A obsolete 0 serverside 1 meaning "Used by the Cloud Files filter, for files managed by a sync engine such as Microsoft OneDrive."]\
APPEXECLINK [list hex 0x8000001B obsolete 0 serverside 1 meaning "Used by Universal Windows Platform (UWP) packages to encode information that allows the application to be launched by CreateProcess."]\
PROJFS [list hex 0x9000001C obsolete 0 serverside 1 meaning "Used by the Windows Projected File System filter, for files managed by a user mode provider such as VFS for Git."]\
LX_SYMLINK [list hex 0xA000001D obsolete 0 serverside 1 meaning "Used by the Windows Subsystem for Linux (WSL) to represent a UNIX symbolic link."]\
STORAGE_SYNC [list hex 0x8000001E obsolete 0 serverside 1 meaning "Used by the Azure File Sync (AFS) filter."]\
WCI_TOMBSTONE [list hex 0xA000001F obsolete 0 serverside 1 meaning "Used by the Windows Container Isolation filter."]\
UNHANDLED [list hex 0x80000020 obsolete 0 serverside 1 meaning "Used by the Windows Container Isolation filter."]\
ONEDRIVE [list hex 0x80000021 obsolete 0 serverside 0 meaning "Not used"]\
PROJFS_TOMBSTONE [list hex 0xA0000022 obsolete 0 serverside 1 meaning "Used by the Windows Projected File System filter, for files managed by a user mode provider such as VFS for Git."]\
AF_UNIX [list hex 0x80000023 obsolete 0 serverside 1 meaning "Used by the Windows Subsystem for Linux (WSL) to represent a UNIX domain socket."]\
LX_FIFO [list hex 0x80000024 obsolete 0 serverside 1 meaning "Used by the Windows Subsystem for Linux (WSL) to represent a UNIX FIFO (named pipe)."]\
LX_CHR [list hex 0x80000025 obsolete 0 serverside 1 meaning "Used by the Windows Subsystem for Linux (WSL) to represent a UNIX character special file."]\
LX_BLK [list hex 0x80000026 obsolete 0 serverside 1 meaning "Used by the Windows Subsystem for Linux (WSL) to represent a UNIX block special file."]\
WCI_LINK [list hex 0xA0000027 obsolete 0 serverside 1 meaning "Used by the Windows Container Isolation filter."]\
WCI_LINK_1 [list hex 0xA0001027 obsolete 0 serverside 1 meaning "Used by the Windows Container Isolation filter."]\
]
variable win_reparse_tags_by_int
dict for {k v} $win_reparse_tags {
set intkey [expr {[dict get $v hex]}]
set info [dict merge [dict create tag $k] $v] ;#put tag at front
dict set win_reparse_tags_by_int $intkey $info
}
#https://stackoverflow.com/questions/46383428/get-the-immediate-target-path-from-symlink-reparse-point
#need to call twapi::create_file with FILE_FLAG_OPEN_REPARSE_POINT 0x00200000
#then twapi::device_ioctl (win32 DeviceIoControl)
#then parse buffer somehow (binary scan..)
#https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b41f1cbf-10df-4a47-98d4-1c52a833d913
proc Get_attributes_from_iteminfo {args} {
variable win_reparse_tags_by_int
set argd [punk::args::get_dict {
@id -id ::punk::du::lib::Get_attributes_from_iteminfo
-debug -default 0 -help "set 1 for raw data on -debugchannel (default stderr)"
-debugchannel -default stderr -help "channel to write debug output, or none to append to output"
@values -min 1 -max 1
iteminfo -help "iteminfo dict as set by 'twapi::find_file_next <iterator> iteminfo'"
} $args]
set opts [dict get $argd opts]
set iteminfo [dict get $argd values iteminfo]
set opt_debug [dict get $opts -debug]
set opt_debugchannel [dict get $opts -debugchannel]
#-longname is placeholder - caller needs to set
set result [dict create -archive 0 -hidden 0 -longname [dict get $iteminfo name] -readonly 0 -shortname {} -system 0]
if {$opt_debug} {
set dbg "iteminfo returned by find_file_open\n"
append dbg [pdict -channel none iteminfo]
if {$opt_debugchannel eq "none"} {
dict set result -debug $dbg
} else {
puts -nonewline $opt_debugchannel $dbg
}
}
set attrinfo [decode_win_attributes [dict get $iteminfo attrs]]
if {"hidden" in $attrinfo} {
dict set result -hidden 1
}
if {"system" in $attrinfo} {
dict set result -system 1
}
if {"readonly" in $attrinfo} {
dict set result -readonly 1
}
dict set result -shortname [dict get $iteminfo altname]
dict set result -fileattributes $attrinfo
if {"reparse_point" in $attrinfo} {
#the twapi API splits this 32bit value for us
set low_word [dict get $iteminfo reserve0]
set high_word [dict get $iteminfo reserve1]
# 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
# 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
#+-+-+-+-+-----------------------+-------------------------------+
#|M|R|N|R| Reserved bits | Reparse tag value |
#+-+-+-+-+-----------------------+-------------------------------+
#todo - is_microsoft from first bit of high_word
set low_int [expr {$low_word}] ;#review - int vs string rep for dict key lookup? does it matter?
if {[dict exists $win_reparse_tags_by_int $low_int]} {
dict set result -reparseinfo [dict get $win_reparse_tags_by_int $low_int]
} else {
dict set result -reparseinfo [dict create tag "<UNKNOWN>" hex 0x[format %X $low_int] meaning "unknown reparse tag int:$low_int"]
}
}
dict set result -raw $iteminfo
return $result
}
proc attributes_twapi {args} {
set argd [punk::args::get_dict {
@id -id ::punk::du::lib::attributes_twapi
-debug -default 0 -help "set 1 for raw data on -debugchannel (default stderr)"
-debugchannel -default stderr -help "channel to write debug output, or none to append to output"
-detail -default basic -choices {basic full} -help "full returns also the altname/shortname field"
@values -min 1 -max 1
path -help "path to file or folder for which to retrieve attributes"
} $args]
set opts [dict get $argd opts]
set path [dict get $argd values path]
set opt_detail [dict get $opts -detail]
set opt_debug [dict get $opts -debug]
set opt_debugchannel [dict get $opts -debugchannel]
try { try {
set iterator [twapi::find_file_open $path -detail $detail] ;# -detail full only adds data to the altname field set iterator [twapi::find_file_open $path -detail $opt_detail] ;# -detail full only adds data to the altname field
if {[twapi::find_file_next $iterator iteminfo]} { if {[twapi::find_file_next $iterator iteminfo]} {
set attrinfo [decode_win_attributes [dict get $iteminfo attrs]] set result [Get_attributes_from_iteminfo -debug $opt_debug -debugchannel $opt_debugchannel $iteminfo]
set result [dict create -archive 0 -hidden 0 -longname $path -readonly 0 -shortname {} -system 0]
if {"hidden" in $attrinfo} {
dict set result -hidden 1
}
if {"system" in $attrinfo} {
dict set result -system 1
}
if {"readonly" in $attrinfo} {
dict set result -readonly 1
}
dict set result -shortname [dict get $iteminfo altname]
dict set result -rawflags $attrinfo
set extras [list]
#foreach prop {ctime atime mtime size} {
# lappend extras $prop [dict get $iteminfo $prop]
#}
#dict set result -extras $extras
dict set result -raw $iteminfo
return $result return $result
} else { } else {
error "could not read attributes for $path" error "could not read attributes for $path"
@ -519,13 +649,14 @@ namespace eval punk::du {
} }
#todo - review 'errors' key. We have errors relating to containing folder and args vs per child-item errors - additional key needed? #todo - review 'errors' key. We have errors relating to containing folder and args vs per child-item errors - additional key needed?
namespace export du_dirlisting_twapi du_dirlisting_generic du_dirlisting_unix du_dirlisting_undecided namespace export attributes_twapi du_dirlisting_twapi du_dirlisting_generic du_dirlisting_unix du_dirlisting_undecided du_dirlisting_tclvfs
# get listing without using unix-tools (may not be installed on the windows system) # get listing without using unix-tools (may not be installed on the windows system)
# this dirlisting is customised for du - so only retrieves dirs,files,filesizes (minimum work needed to perform du function) # this dirlisting is customised for du - so only retrieves dirs,files,filesizes (minimum work needed to perform du function)
# This also preserves path rep for elements in the dirs/folders keys etc - which can make a big difference in performance # This also preserves path rep for elements in the dirs/folders keys etc - which can make a big difference in performance
proc du_dirlisting_twapi {folderpath args} { proc du_dirlisting_twapi {folderpath args} {
set defaults [dict create\ set defaults [dict create\
-glob *\ -glob *\
-filedebug 0\
-with_sizes 1\ -with_sizes 1\
-with_times 1\ -with_times 1\
] ]
@ -534,6 +665,9 @@ namespace eval punk::du {
set opt_glob [dict get $opts -glob] set opt_glob [dict get $opts -glob]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_with_sizes [dict get $opts -with_sizes] set opt_with_sizes [dict get $opts -with_sizes]
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_filedebug [dict get $opts -filedebug] ;#per file
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set ftypes [list f d l] set ftypes [list f d l]
if {"$opt_with_sizes" in {0 1}} { if {"$opt_with_sizes" in {0 1}} {
#don't use string is boolean - (f false vs f file!) #don't use string is boolean - (f false vs f file!)
@ -705,6 +839,8 @@ namespace eval punk::du {
set alltimes [dict create] set alltimes [dict create]
set links [list] set links [list]
set linkinfo [dict create]
set debuginfo [dict create]
set flaggedhidden [list] set flaggedhidden [list]
set flaggedsystem [list] set flaggedsystem [list]
set flaggedreadonly [list] set flaggedreadonly [list]
@ -717,25 +853,18 @@ namespace eval punk::du {
continue continue
} }
set tail_altname [dict get $iteminfo altname] ;#altname of tail - not whole path set tail_altname [dict get $iteminfo altname] ;#altname of tail - not whole path
set attrinfo [decode_win_attributes [dict get $iteminfo attrs]] #set attrinfo [decode_win_attributes [dict get $iteminfo attrs]]
#puts stderr "$iteminfo"
#puts stderr "$nm -> [dict get $iteminfo attrs] -> $attrinfo"
set ftype "" set ftype ""
#attributes applicable to any classification #attributes applicable to any classification
set fullname [file_join_one $folderpath $nm] set fullname [file_join_one $folderpath $nm]
if {"hidden" in $attrinfo} { set attrdict [Get_attributes_from_iteminfo -debug $opt_filedebug -debugchannel none $iteminfo] ;#-debugchannel none puts -debug key in the resulting dict
lappend flaggedhidden $fullname set file_attributes [dict get $attrdict -fileattributes]
}
if {"system" in $attrinfo} {
lappend flaggedsystem $fullname
}
if {"readonly" in $attrinfo} {
lappend flaggedreadonly $fullname
}
set linkdata [dict create]
# -----------------------------------------------------------
#main classification #main classification
if {"reparse_point" in $attrinfo} { if {"reparse_point" in $file_attributes} {
#this concept doesn't correspond 1-to-1 with unix links #this concept doesn't correspond 1-to-1 with unix links
#https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points #https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points
#review - and see which if any actually belong in the links key of our return #review - and see which if any actually belong in the links key of our return
@ -758,17 +887,27 @@ namespace eval punk::du {
#Note also - a shortcut created in explorer with drag and drop to an existant folder is a different animal to a symlink (file with .lnk extension) even though it looks the same in explorer window. #Note also - a shortcut created in explorer with drag and drop to an existant folder is a different animal to a symlink (file with .lnk extension) even though it looks the same in explorer window.
# #
#links are techically files too, whether they point to a file/dir or nothing. #links are techically files too, whether they point to a file/dir or nothing.
lappend links $fullname lappend links $fullname
set ftype "l" set ftype "l"
} elseif {"directory" in $attrinfo} { dict set linkdata linktype reparse_point
dict set linkdata reparseinfo [dict get $attrdict -reparseinfo]
if {"directory" ni $file_attributes} {
dict set linkdata target_type file
}
}
if {"directory" in $file_attributes} {
if {$nm in {. ..}} { if {$nm in {. ..}} {
continue continue
} }
lappend dirs $fullname if {"reparse_point" ni $file_attributes} {
set ftype "d" lappend dirs $fullname
} else { set ftype "d"
} else {
#other mechanisms can't immediately classify a link as file vs directory - so we don't return this info in the main dirs/files collections
dict set linkdata target_type directory
}
}
if {"reparse_point" ni $file_attributes && "directory" ni $file_attributes} {
#review - is anything that isn't a reparse_point or a directory, some sort of 'file' in this context? What about the 'device' attribute? Can that occur in a directory listing of some sort? #review - is anything that isn't a reparse_point or a directory, some sort of 'file' in this context? What about the 'device' attribute? Can that occur in a directory listing of some sort?
lappend files $fullname lappend files $fullname
if {"f" in $sized_types} { if {"f" in $sized_types} {
@ -776,6 +915,17 @@ namespace eval punk::du {
} }
set ftype "f" set ftype "f"
} }
# -----------------------------------------------------------
if {[dict get $attrdict -hidden]} {
lappend flaggedhidden $fullname
}
if {[dict get $attrdict -system]} {
lappend flaggedsystem $fullname
}
if {[dict get $attrdict -readonly]} {
lappend flaggedreadonly $fullname
}
if {$ftype in $sized_types} { if {$ftype in $sized_types} {
dict set allsizes $fullname [dict create bytes [dict get $iteminfo size]] dict set allsizes $fullname [dict create bytes [dict get $iteminfo size]]
} }
@ -789,6 +939,12 @@ namespace eval punk::du {
m [twapi::large_system_time_to_secs_since_1970 [dict get $iteminfo mtime]]\ m [twapi::large_system_time_to_secs_since_1970 [dict get $iteminfo mtime]]\
] ]
} }
if {[dict size $linkdata]} {
dict set linkinfo $fullname $linkdata
}
if {[dict exists $attrdict -debug]} {
dict set debuginfo $fullname [dict get $attrdict -debug]
}
} }
twapi::find_file_close $iterator twapi::find_file_close $iterator
set vfsmounts [get_vfsmounts_in_folder $folderpath] set vfsmounts [get_vfsmounts_in_folder $folderpath]
@ -799,7 +955,7 @@ namespace eval punk::du {
#also determine whether vfs. file system x is *much* faster than file attributes #also determine whether vfs. file system x is *much* faster than file attributes
#whether or not there is a corresponding file/dir add any applicable mountpoints for the containing folder #whether or not there is a corresponding file/dir add any applicable mountpoints for the containing folder
return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes $filesizes sizes $allsizes times $alltimes flaggedhidden $flaggedhidden flaggedsystem $flaggedsystem flaggedreadonly $flaggedreadonly altname $altname opts $effective_opts errors $errors] return [list dirs $dirs vfsmounts $vfsmounts links $links linkinfo $linkinfo files $files filesizes $filesizes sizes $allsizes times $alltimes flaggedhidden $flaggedhidden flaggedsystem $flaggedsystem flaggedreadonly $flaggedreadonly altname $altname opts $effective_opts debuginfo $debuginfo errors $errors]
} }
proc get_vfsmounts_in_folder {folderpath} { proc get_vfsmounts_in_folder {folderpath} {
set vfsmounts [list] set vfsmounts [list]
@ -911,55 +1067,65 @@ namespace eval punk::du {
#note - with this design, we can't glob for all except dotfiles - this is for cross-platform consistency and efficiency (Review). #note - with this design, we can't glob for all except dotfiles - this is for cross-platform consistency and efficiency (Review).
#dotfiles aren't considered hidden on all platforms #dotfiles aren't considered hidden on all platforms
#some sort of antiglob is a possible enhancement and more consistent with the view that leading dot should be treated as any other filename character in this context. #some sort of antiglob is a possible enhancement and more consistent with the view that leading dot should be treated as any other filename character in this context.
if {$opt_glob eq "*"} { if {"windows" eq $::tcl_platform(platform)} {
#Note - glob operations seem to be faster than looped tests like 'file isdirectory' & 'file readlink' if {$opt_glob eq "*"} {
#set parent [lindex $folders $folderidx] #Note - glob operations seem to be faster than looped tests like 'file isdirectory' & 'file readlink'
set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} * .*] #set parent [lindex $folders $folderidx]
#set hdirs {} set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} * .*]
set dirs [glob -nocomplain -dir $folderpath -types d * .*] set dirs [glob -nocomplain -dir $folderpath -types d * .*]
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} * .*]
#set hlinks {}
set links [glob -nocomplain -dir $folderpath -types l * .*] ;#links may have dupes - we don't care. struct::set difference will remove (?)
#set links [lsort -unique [concat $hlinks $links[unset links]]]
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} * .*]
#set hfiles {}
set files [glob -nocomplain -dir $folderpath -types f * .*]
#set files {}
} else {
set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} $opt_glob]
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob]
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} $opt_glob] set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} * .*]
set links [glob -nocomplain -dir $folderpath -types l $opt_glob] ;#links may have dupes - we don't care. struct::set difference will remove (?) set links [glob -nocomplain -dir $folderpath -types l * .*] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} * .*]
set files [glob -nocomplain -dir $folderpath -types f * .*]
} else {
set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} $opt_glob]
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob]
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} $opt_glob]
set links [glob -nocomplain -dir $folderpath -types l $opt_glob] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} $opt_glob]
set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
}
} else {
set hdirs {}
set hfiles {}
set hlinks {}
if {$opt_glob eq "*"} {
#Note - glob operations seem to be faster than looped tests like 'file isdirectory' & 'file readlink'
#set parent [lindex $folders $folderidx]
set dirs [glob -nocomplain -dir $folderpath -types d * .*]
set links [glob -nocomplain -dir $folderpath -types l * .*] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
set files [glob -nocomplain -dir $folderpath -types f * .*]
} else {
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob]
set links [glob -nocomplain -dir $folderpath -types l $opt_glob] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
}
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} $opt_glob]
set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
} }
#note struct::set difference produces unordered result #note struct::set difference produces unordered result
#struct::set difference removes duplicates (but not always.. e.g if using tcl impl and 2nd element empty!) #struct::set difference removes duplicates (but not always.. e.g if using tcl impl and 2nd element empty!)
#relying on struct::set to remove dupes is somewhat risky. It is not well documented - and behaviour of dupes in inputs is underspecified as it appears to be intended for mathematical 'sets' #relying on struct::set to remove dupes is somewhat risky.
#It is not well documented - and behaviour of dupes in inputs is underspecified as it appears to be intended for mathematical 'sets' ie lists without dupes
#for this reason we must use the wrapper punk::lib::struct_set_diff_unique, which will use the well behaved critcl for speed if avail, but fall back to a deduping tcl version
#remove links and . .. from directories, remove links from files #remove links and . .. from directories, remove links from files
#ideally we would like to classify links by whether they point to files vs dirs - but there are enough cross-platform differences that we will have to leave it to the caller to sort out for now.
#struct::set will affect order: tcl vs critcl give different ordering! #struct::set will affect order: tcl vs critcl give different ordering!
set files [struct::set difference [concat $hfiles $files[unset files]] $links] set files [punk::lib::struct_set_diff_unique [list {*}$hfiles {*}$files[unset files]] $links]
set dirs [struct::set difference [concat $hdirs $dirs[unset dirs]] [concat $links [list [file join $folderpath .] [file join $folderpath ..] ]]] set dirs [punk::lib::struct_set_diff_unique [list {*}$hdirs {*}$dirs[unset dirs] ] [list {*}$links [file join $folderpath .] [file join $folderpath ..]]]
#set links [lsort -unique [concat $links $hlinks]]
#---- #----
set mdata_lists [du_get_metadata_lists $sized_types $timed_types $files $dirs $links] set mdata_lists [du_get_metadata_lists $sized_types $timed_types $files $dirs $links]
set flaggedhidden [list {*}$hdirs {*}$hfiles {*}$hlinks]
if {"windows" eq $::tcl_platform(platform)} { #unix dotted files/folders are not 'flagged' as such - it's a convention - so the client can use the same convention to decide if something is hidden
set flaggedhidden [concat $hdirs $hfiles $hlinks] #this allows us to return less data - but more importantly - reserve flaggedhidden for systems where such a flag exists and avoid conflating the different concepts of what is hidden
} else {
#unix dotted files/folders are not 'flagged' as such - it's a convention - so the client can use the same convention to decide if something is hidden
#this allows us to return less data - but more importantly - reserve flaggedhidden for systems where such a flag exists and avoid conflating the different concepts of what is hidden
set flaggedhidden {}
}
set vfsmounts [get_vfsmounts_in_folder $folderpath] set vfsmounts [get_vfsmounts_in_folder $folderpath]
@ -967,7 +1133,7 @@ namespace eval punk::du {
dict set effective_opts -with_times $timed_types dict set effective_opts -with_times $timed_types
dict set effective_opts -with_sizes $sized_types dict set effective_opts -with_sizes $sized_types
return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden {} flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors] return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden $flaggedhidden flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors]
} }
#zipfs attributes/behaviour fairly different to tclvfs - keep separate #zipfs attributes/behaviour fairly different to tclvfs - keep separate
@ -1068,21 +1234,21 @@ namespace eval punk::du {
#if {[punk::mix::base::lib::path_a_above_b $folderpath "//zipfs:/"]} {} #if {[punk::mix::base::lib::path_a_above_b $folderpath "//zipfs:/"]} {}
#zipfs files also reported as links by glob - review - should we preserve this in response? #todo - hidden? not returned in attributes on windows at least.
#zipfs files also reported as links by glob - review - should we preserve this in response? (2024 unable to duplicate)
if {$opt_glob eq "*"} { if {$opt_glob eq "*"} {
set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs
#set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove
set links [list]
set files [glob -nocomplain -dir $folderpath -types f * .*] ;# also returns links to files set files [glob -nocomplain -dir $folderpath -types f * .*] ;# also returns links to files
} else { } else {
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob] set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob]
#set links [glob -nocomplain -dir $folderpath -types l $opt_glob] set links [glob -nocomplain -dir $folderpath -types l $opt_glob]
set links [list]
set files [glob -nocomplain -dir $folderpath -types f $opt_glob] set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
} }
#remove any links from our dirs and files collections #remove any links from our dirs and files collections
set dirs [struct::set difference $dirs[unset dirs] [concat $links [list [file join $folderpath .] [file join $folderpath ..] ]]] #see du_dirlisting_generic re struct::set difference issues
set files [struct::set difference $files[unset files] $links] set dirs [punk::lib::struct_set_diff_unique $dirs[unset dirs] [list {*}$links [file join $folderpath .] [file join $folderpath ..]]]
set files [punk::lib::struct_set_diff_unique $files[unset files] $links]
#nested vfs mount.. REVIEW - does anything need special handling? #nested vfs mount.. REVIEW - does anything need special handling?
@ -1145,34 +1311,63 @@ namespace eval punk::du {
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
#at least some vfs on windows seem to support the -hidden attribute
#we are presuming glob will accept the -types hidden option for all vfs - even if it doesn't really apply REVIEW
#The extra globs aren't nice - but hopefully the vfs is reasonably performant (?)
set errors [dict create] set errors [dict create]
if {$opt_glob eq "*"} { if {"windows" eq $::tcl_platform(platform)} {
set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs if {$opt_glob eq "*"} {
#review - how are links handled in vfs? presumably if the vfs is a full implementation they should work at least within the vfs? set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} * .*]
set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs
set files [glob -nocomplain -dir $folderpath -types f * .*] ;# also returns links to files #review - how are links handled in vfs? presumably if the vfs is a full implementation they should work at least within the vfs?
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} * .*]
set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove
set hfiles [glob -nocomplain -dir $folderpath -types {hidden l} * .*]
set files [glob -nocomplain -dir $folderpath -types f * .*] ;# also returns links to files
} else {
set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} $opt_glob]
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob]
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} $opt_glob]
set links [glob -nocomplain -dir $folderpath -types l $opt_glob]
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} $opt_glob]
set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
}
} else { } else {
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob] #we leave it to the ui on unix to classify dotfiles as hidden
set links [glob -nocomplain -dir $folderpath -types l $opt_glob] set hdirs {}
set files [glob -nocomplain -dir $folderpath -types f $opt_glob] set hfiles {}
set hlinks {}
if {$opt_glob eq "*"} {
set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs
#review - how are links handled in vfs? presumably if the vfs is a full implementation they should work at least within the vfs?
set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove
set files [glob -nocomplain -dir $folderpath -types f * .*] ;# also returns links to files
} else {
set dirs [glob -nocomplain -dir $folderpath -types d $opt_glob]
set links [glob -nocomplain -dir $folderpath -types l $opt_glob]
set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
}
} }
#remove any links from our dirs and files collections #remove any links from our dirs and files collections
set dirs [struct::set difference $dirs[unset dirs] [concat $links [list [file join $folderpath .] [file join $folderpath ..] ]]] #see du_dirlisting_generic re struct::set difference issues
set files [struct::set difference $files[unset files] $links] set dirs [punk::lib::struct_set_diff_unique $dirs[unset dirs] [list {*}$links [file join $folderpath .] [file join $folderpath ..] ]]
set files [punk::lib::struct_set_diff_unique $files[unset files] $links]
#nested vfs mount.. REVIEW - does anything need special handling? #nested vfs mount.. REVIEW - does anything need special handling?
set vfsmounts [get_vfsmounts_in_folder $folderpath] set vfsmounts [get_vfsmounts_in_folder $folderpath]
set mdata_lists [du_get_metadata_lists $sized_types $timed_types $files $dirs $links] set mdata_lists [du_get_metadata_lists $sized_types $timed_types $files $dirs $links]
set flaggedhidden [list {*}$hdirs {*}$hfiles {*}$hlinks]
set effective_opts $opts set effective_opts $opts
dict set effective_opts -with_times $timed_types dict set effective_opts -with_times $timed_types
dict set effective_opts -with_sizes $sized_types dict set effective_opts -with_sizes $sized_types
return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden {} flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors] return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden $flaggedhidden flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors]
} }
#we can halve the number of round trips on unix-like systems, where 'hidden' always corresponds to dotted files #we can halve the number of round trips on unix-like systems, where 'hidden' always corresponds to dotted files
#but we don't classify as such anyway. (leave for UI)
proc du_dirlisting_unix {folderpath args} { proc du_dirlisting_unix {folderpath args} {
set defaults [dict create\ set defaults [dict create\
-glob *\ -glob *\
@ -1224,6 +1419,9 @@ namespace eval punk::du {
} }
#this is not consistent with standard way * works for unix folders - but it is consistent with behaviour of this facility on windows #this is not consistent with standard way * works for unix folders - but it is consistent with behaviour of this facility on windows
#we don't classify anything as 'flaggedhidden' on unix.
#it is a convention for dotfiles rather than a flag - and we'll leave the distinction for the display library
#This
if {$opt_glob eq "*"} { if {$opt_glob eq "*"} {
set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs set dirs [glob -nocomplain -dir $folderpath -types d * .*] ;# also returns links to dirs
set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove set links [glob -nocomplain -dir $folderpath -types l * .*] ;# links may have dupes - we don't care. struct::set difference will remove
@ -1234,8 +1432,9 @@ namespace eval punk::du {
set files [glob -nocomplain -dir $folderpath -types f $opt_glob] set files [glob -nocomplain -dir $folderpath -types f $opt_glob]
} }
#remove any links from our dirs and files collections #remove any links from our dirs and files collections
set dirs [struct::set difference $dirs[unset dirs] [concat $links [list [file join $folderpath .] [file join $folderpath ..] ]]] #see du_dirlisting_generic re struct::set difference issues
set files [struct::set difference $files[unset files] $links] set dirs [punk::lib::struct_set_diff_unique $dirs[unset dirs] [list {*}$links [file join $folderpath .] [file join $folderpath ..] ]]
set files [punk::lib::struct_set_diff_unique $files[unset files] $links]
set vfsmounts [get_vfsmounts_in_folder $folderpath] set vfsmounts [get_vfsmounts_in_folder $folderpath]
set mdata_lists [du_get_metadata_lists $sized_types $timed_types $files $dirs $links] set mdata_lists [du_get_metadata_lists $sized_types $timed_types $files $dirs $links]
@ -1251,7 +1450,7 @@ namespace eval punk::du {
#return fsizes,allsizes,alltimes metadata in same order as files,dirs,links lists - if specified in sized_types #return fsizes,allsizes,alltimes metadata in same order as files,dirs,links lists - if specified in sized_types
proc du_get_metadata_lists {sized_types timed_types files dirs links} { proc du_get_metadata_lists {sized_types timed_types files dirs links} {
set meta_dict [dict create] set meta_dict [dict create]
set meta_types [concat $sized_types $timed_types] set meta_types [list {*}$sized_types {*}$timed_types]
#known tcl stat keys 2023 - review #known tcl stat keys 2023 - review
set empty_stat_dict [dict create atime {} ctime {} dev {} gid {} ino {} mode {} mtime {} nlink {} size {} type {} uid {}] set empty_stat_dict [dict create atime {} ctime {} dev {} gid {} ino {} mode {} mtime {} nlink {} size {} type {} uid {}]
#make sure we call file stat only once per item #make sure we call file stat only once per item
@ -1264,6 +1463,7 @@ namespace eval punk::du {
if {![catch {file stat $path arrstat} errM]} { if {![catch {file stat $path arrstat} errM]} {
dict set meta_dict $path [dict create shorttype $ft {*}[array get arrstat]] dict set meta_dict $path [dict create shorttype $ft {*}[array get arrstat]]
} else { } else {
puts stderr "du_get_metadata_lists: file stat $path error: $errM"
dict lappend errors $path "file stat error: $errM" dict lappend errors $path "file stat error: $errM"
dict set meta_dict $path [dict create shorttype $ft {*}$empty_stat_dict] dict set meta_dict $path [dict create shorttype $ft {*}$empty_stat_dict]
} }
@ -1282,6 +1482,9 @@ namespace eval punk::du {
if {$ft eq "f"} { if {$ft eq "f"} {
#subst with na if empty? #subst with na if empty?
lappend fsizes [dict get $pathinfo size] lappend fsizes [dict get $pathinfo size]
if {[dict get $pathinfo size] eq ""} {
puts stderr "du_get_metadata_lists: fsize $path is empty!"
}
} }
} }
if {$ft in $timed_types} { if {$ft in $timed_types} {
@ -1291,7 +1494,7 @@ namespace eval punk::du {
#todo - fix . The list lengths will presumably match but have empty values if failed to stat #todo - fix . The list lengths will presumably match but have empty values if failed to stat
if {"f" in $sized_types} { if {"f" in $sized_types} {
if {[llength $fsizes] ne [llength $files]} { if {[llength $fsizes] ne [llength $files]} {
dict lappend errors $folderpath "failed to retrieve all file sizes" dict lappend errors general "failed to retrieve all file sizes"
} }
} }
return [dict create fsizes $fsizes allsizes $allsizes alltimes $alltimes] return [dict create fsizes $fsizes allsizes $allsizes alltimes $alltimes]

219
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm vendored

@ -9,7 +9,7 @@
# @@ Meta Begin # @@ Meta Begin
# Application punk::fileline 0.1.0 # Application punk::fileline 0.1.0
# Meta platform tcl # Meta platform tcl
# Meta license BSD # Meta license BSD
# @@ Meta End # @@ Meta End
@ -20,7 +20,7 @@
#[manpage_begin punkshell_module_punk::fileline 0 0.1.0] #[manpage_begin punkshell_module_punk::fileline 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {file line-handling utilities}] [comment {-- Name section and table of contents description --}] #[titledesc {file line-handling utilities}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk fileline}] [comment {-- Description at end of page heading --}] #[moddesc {punk fileline}] [comment {-- Description at end of page heading --}]
#[require punk::fileline] #[require punk::fileline]
#[keywords module text parse file encoding BOM] #[keywords module text parse file encoding BOM]
#[description] #[description]
@ -33,7 +33,7 @@
#[para]Utilities for in-memory analysis of text file data as both line data and byte/char-counted data whilst preserving the line-endings (even if mixed) #[para]Utilities for in-memory analysis of text file data as both line data and byte/char-counted data whilst preserving the line-endings (even if mixed)
#[para]This is important for certain text files where examining the number of chars/bytes is important #[para]This is important for certain text files where examining the number of chars/bytes is important
#[para]For example - windows .cmd/.bat files need some byte counting to determine if labels lie on chunk boundaries and need to be moved. #[para]For example - windows .cmd/.bat files need some byte counting to determine if labels lie on chunk boundaries and need to be moved.
#[para]This chunk-size counting will depend on the character encoding. #[para]This chunk-size counting will depend on the character encoding.
#[para]Despite including the word 'file', the library doesn't necessarily deal with reading/writing to the filesystem - #[para]Despite including the word 'file', the library doesn't necessarily deal with reading/writing to the filesystem -
#[para]The raw data can be supplied as a string, or loaded from a file using punk::fileline::get_textinfo -file <filename> #[para]The raw data can be supplied as a string, or loaded from a file using punk::fileline::get_textinfo -file <filename>
#[subsection Concepts] #[subsection Concepts]
@ -42,13 +42,13 @@
# package require punk::fileline # package require punk::fileline
# package require fileutil # package require fileutil
# set rawdata [lb]fileutil::cat data.txt -translation binary[rb] # set rawdata [lb]fileutil::cat data.txt -translation binary[rb]
# punk::fileline::class::textinfo create obj_data $rawdata # punk::fileline::class::textinfo create obj_data $rawdata
# puts stdout [lb]obj_data linecount[rb] # puts stdout [lb]obj_data linecount[rb]
#[example_end] #[example_end]
#[subsection Notes] #[subsection Notes]
#[para]Line records are referred to by a zero-based index instead of a one-based index as is commonly used when displaying files. #[para]Line records are referred to by a zero-based index instead of a one-based index as is commonly used when displaying files.
#[para]This is for programming consistency and convenience, and the module user should do their own conversion to one-based indexing for line display or messaging if desired. #[para]This is for programming consistency and convenience, and the module user should do their own conversion to one-based indexing for line display or messaging if desired.
#[para]No support for lone carriage-returns being interpreted as line-endings. #[para]No support for lone carriage-returns being interpreted as line-endings.
#[para]CR line-endings that are intended to be interpreted as such should be mapped to something else before the data is supplied to this module. #[para]CR line-endings that are intended to be interpreted as such should be mapped to something else before the data is supplied to this module.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -141,7 +141,7 @@ namespace eval punk::fileline::class {
variable o_line_epoch variable o_line_epoch
variable o_payloadlist variable o_payloadlist
variable o_linemap variable o_linemap
variable o_LF_C variable o_LF_C
variable o_CRLF_C variable o_CRLF_C
@ -158,7 +158,7 @@ namespace eval punk::fileline::class {
#[para] Constructor for textinfo object which represents a chunk or all of a file #[para] Constructor for textinfo object which represents a chunk or all of a file
#[para] datachunk should be passed with the file data including line-endings as-is for full functionality. ie use something like: #[para] datachunk should be passed with the file data including line-endings as-is for full functionality. ie use something like:
#[example_begin] #[example_begin]
# fconfigure $fd -translation binary # chan configure $fd -translation binary
# set chunkdata [lb]read $fd[rb]] # set chunkdata [lb]read $fd[rb]]
#or #or
# set chunkdata [lb]fileutil::cat <filename> -translation binary[rb] # set chunkdata [lb]fileutil::cat <filename> -translation binary[rb]
@ -191,7 +191,7 @@ namespace eval punk::fileline::class {
set o_bom "" ;#review set o_bom "" ;#review
set o_chunk $datachunk set o_chunk $datachunk
set o_line_epoch [list] set o_line_epoch [list]
set o_chunk_epoch [list "fromchunkchange-at-[clock micros]"] set o_chunk_epoch [list "fromchunkchange-at-[clock micros]"]
set crlf_lf_placeholders [list \uFFFF \uFFFE] ;#defaults - if already exist in file - error out with message set crlf_lf_placeholders [list \uFFFF \uFFFE] ;#defaults - if already exist in file - error out with message
set defaults [dict create\ set defaults [dict create\
@ -206,11 +206,11 @@ namespace eval punk::fileline::class {
} }
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_substitutionmap [dict get $opts -substitutionmap] ;#review - can be done by caller - or a loadable -policy set opt_substitutionmap [dict get $opts -substitutionmap] ;#review - can be done by caller - or a loadable -policy
set opt_crlf_lf_placeholders [dict get $opts -crlf_lf_placeholders] set opt_crlf_lf_placeholders [dict get $opts -crlf_lf_placeholders]
set opt_userid [dict get $opts -userid] set opt_userid [dict get $opts -userid]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
if {[llength $opt_crlf_lf_placeholders] != 2 || [string length [lindex $opt_crlf_lf_placeholders 0]] !=1 || [string length [lindex $opt_crlf_lf_placeholders 1]] !=1} { if {[llength $opt_crlf_lf_placeholders] != 2 || [string length [lindex $opt_crlf_lf_placeholders 0]] !=1 || [string length [lindex $opt_crlf_lf_placeholders 1]] !=1} {
error "textinfo::constructor error: -crlf_lf_placeholders requires a list of exactly 2 chars" error "textinfo::constructor error: -crlf_lf_placeholders requires a list of exactly 2 chars"
@ -261,7 +261,7 @@ namespace eval punk::fileline::class {
#[call class::textinfo [method chunk] [arg chunkstart] [arg chunkend]] #[call class::textinfo [method chunk] [arg chunkstart] [arg chunkend]]
#[para]Return a range of bytes from the underlying raw chunk data. #[para]Return a range of bytes from the underlying raw chunk data.
#[para] e.g The following retrieves the entire chunk #[para] e.g The following retrieves the entire chunk
#[para] objName chunk 0 end #[para] objName chunk 0 end
return [string range $o_chunk $chunkstart $chunkend] return [string range $o_chunk $chunkstart $chunkend]
} }
method chunklen {} { method chunklen {} {
@ -273,7 +273,7 @@ namespace eval punk::fileline::class {
method chunk_boundary_display {chunkstart chunkend chunksize args} { method chunk_boundary_display {chunkstart chunkend chunksize args} {
#*** !doctools #*** !doctools
#[call class::textinfo [method chunk_boundary_display]] #[call class::textinfo [method chunk_boundary_display]]
#[para]Returns a string displaying the boundaries at chunksize bytes between chunkstart and chunkend #[para]Returns a string displaying the boundaries at chunksize bytes between chunkstart and chunkend
#[para]Defaults to using ansi colour if punk::ansi module is available. Use -ansi 0 to disable colour #[para]Defaults to using ansi colour if punk::ansi module is available. Use -ansi 0 to disable colour
set opts [dict create\ set opts [dict create\
-ansi $::punk::fileline::ansi::enabled\ -ansi $::punk::fileline::ansi::enabled\
@ -290,7 +290,6 @@ namespace eval punk::fileline::class {
-showconfig 0\ -showconfig 0\
-boundaryheader {Boundary %i% at %b%}\ -boundaryheader {Boundary %i% at %b%}\
] ]
set known_opts [dict keys $defaults]
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-ansi - -offset - -displaybytes - -truncatedmark - -completemark - -moremark - -continuemark - -linemaxwidth - -linebase - -limit - -boundaries - -showconfig - -boundaryheader { -ansi - -offset - -displaybytes - -truncatedmark - -completemark - -moremark - -continuemark - -linemaxwidth - -linebase - -limit - -boundaries - -showconfig - -boundaryheader {
@ -332,7 +331,7 @@ namespace eval punk::fileline::class {
if {$opt_ansi} { if {$opt_ansi} {
set ::punk::fileline::ansi::enabled 1 set ::punk::fileline::ansi::enabled 1
} else { } else {
set ::punk::fileline::ansi::enabled 0 set ::punk::fileline::ansi::enabled 0
} }
if {"::punk::fileline::ansistrip" ne [info commands ::punk::fileline::ansistrip]} { if {"::punk::fileline::ansistrip" ne [info commands ::punk::fileline::ansistrip]} {
proc ::punk::fileline::a {args} { proc ::punk::fileline::a {args} {
@ -351,7 +350,7 @@ namespace eval punk::fileline::class {
} }
proc ::punk::fileline::ansistrip {str} { proc ::punk::fileline::ansistrip {str} {
if {$::punk::fileline::ansi::enabled} { if {$::punk::fileline::ansi::enabled} {
tailcall ::punk::fileline::ansi::ansistrip $str tailcall ::punk::fileline::ansi::ansistrip $str
} else { } else {
return $str return $str
} }
@ -362,10 +361,10 @@ namespace eval punk::fileline::class {
#suport simple end+-int (+-)start(+-)int to set linebase to line corresponding to chunkstart or chunkend #suport simple end+-int (+-)start(+-)int to set linebase to line corresponding to chunkstart or chunkend
#also simple int+int and int-int - nothing more complicated (similar to Tcl lrange etc in that regard) #also simple int+int and int-int - nothing more complicated (similar to Tcl lrange etc in that regard)
#commonly this will be something like -start or -end #commonly this will be something like -start or -end
if {![string is integer -strict $opt_linebase]} { if {![string is integer -strict $opt_linebase]} {
set sign "" set sign ""
set errunrecognised "unrecognised -linebase value '$opt_linebase'. Expected positive or negative integer or -start -start-int -start+int -end -end-int -end+int or -eof (where leading - is optional but probably desirable) " set errunrecognised "unrecognised -linebase value '$opt_linebase'. Expected positive or negative integer or -start -start-int -start+int -end -end-int -end+int or -eof (where leading - is optional but probably desirable) "
if {[string index $opt_linebase 0] eq "-"} { if {[string index $opt_linebase 0] eq "-"} {
set sign - set sign -
set tail [string range $opt_linebase 1 end] set tail [string range $opt_linebase 1 end]
@ -403,7 +402,7 @@ namespace eval punk::fileline::class {
} else { } else {
set linebase $maxline set linebase $maxline
} }
set linebase ${sign}$linebase set linebase ${sign}$linebase
} elseif {[string match start* $tail]} { } elseif {[string match start* $tail]} {
set endmath [string range $tail 5 end] set endmath [string range $tail 5 end]
if {[string length $endmath]} { if {[string length $endmath]} {
@ -490,7 +489,7 @@ namespace eval punk::fileline::class {
set j [expr {$i+1}] set j [expr {$i+1}]
append result [string map [list %b% $b %i% $i %j% $j] $opt_boundaryheader] \n append result [string map [list %b% $b %i% $i %j% $j] $opt_boundaryheader] \n
} }
set low [expr {max(($b - $pre_bytes),0)}] set low [expr {max(($b - $pre_bytes),0)}]
set high [expr {min(($b + $post_bytes),$max_bytes)}] set high [expr {min(($b + $post_bytes),$max_bytes)}]
set lineinfolist [my chunkrange_to_lineinfolist $low $high -show_truncated 1] set lineinfolist [my chunkrange_to_lineinfolist $low $high -show_truncated 1]
@ -504,11 +503,11 @@ namespace eval punk::fileline::class {
set e [dict get $lineinfo end] set e [dict get $lineinfo end]
set boundarymarker "" set boundarymarker ""
set displayidx "" set displayidx ""
set linenum_display $linenum set linenum_display $linenum
if {$s <= $b && $e >= $b} { if {$s <= $b && $e >= $b} {
set idx [expr {$b - $s}] ;#index into whole position in whole line - not so useful if we're viewing a small section of a line set idx [expr {$b - $s}] ;#index into whole position in whole line - not so useful if we're viewing a small section of a line
set char [string index [my line $lineidx] $idx] set char [string index [my line $lineidx] $idx]
set char_display [string map [list \r <CR> \n <LF>] $char] set char_display [string map [list \r <CR> \n <LF>] $char]
if {[dict get $lineinfo is_truncated]} { if {[dict get $lineinfo is_truncated]} {
set tside [dict get $lineinfo truncatedside] set tside [dict get $lineinfo truncatedside]
@ -528,29 +527,29 @@ namespace eval punk::fileline::class {
set linenum_display ${linenum_display},$idx set linenum_display ${linenum_display},$idx
} }
set lhs_status $opt_cmark ;#default set lhs_status $opt_cmark ;#default
set rhs_status $opt_cmark ;#default set rhs_status $opt_cmark ;#default
if {[dict get $lineinfo is_truncated]} { if {[dict get $lineinfo is_truncated]} {
set line [dict get $lineinfo truncated] set line [dict get $lineinfo truncated]
set tside [dict get $lineinfo truncatedside] set tside [dict get $lineinfo truncatedside]
if {"left" in $tside && "right" in $tside } { if {"left" in $tside && "right" in $tside } {
set lhs_status $opt_tmark set lhs_status $opt_tmark
set rhs_status $opt_tmark set rhs_status $opt_tmark
} elseif {"left" in $tside} { } elseif {"left" in $tside} {
set lhs_status $opt_tmark set lhs_status $opt_tmark
} elseif {"right" in $tside} { } elseif {"right" in $tside} {
set rhs_status $opt_tmark set rhs_status $opt_tmark
} }
} else { } else {
set line [my line $lineidx] set line [my line $lineidx]
} }
if {$displayidx ne ""} { if {$displayidx ne ""} {
set line [string replace $line $displayidx $displayidx [a+ White green bold]$char_display[a]] set line [string replace $line $displayidx $displayidx [a+ White green bold]$char_display[a]]
} }
set displayline [string map $le_map $line] set displayline [string map $le_map $line]
lappend result_list [list $linenum_display $boundarymarker $lhs_status $displayline $rhs_status] lappend result_list [list $linenum_display $boundarymarker $lhs_status $displayline $rhs_status]
} }
set title_linenum "LNUM" set title_linenum "LNUM"
set linenums [lsearch -index 0 -all -inline -subindices $result_list *] set linenums [lsearch -index 0 -all -inline -subindices $result_list *]
@ -587,12 +586,12 @@ namespace eval punk::fileline::class {
method line {lineindex} { method line {lineindex} {
#*** !doctools #*** !doctools
#[call class::textinfo [method line] [arg lineindex]] #[call class::textinfo [method line] [arg lineindex]]
#[para]Reconstructs and returns the raw line using the payload and per-line stored line-ending metadata #[para]Reconstructs and returns the raw line using the payload and per-line stored line-ending metadata
#[para]A 'line' may be returned without a line-ending if the unerlying chunk had trailing data without a line-ending (or the chunk was loaded under a non-standard -policy setting) #[para]A 'line' may be returned without a line-ending if the unerlying chunk had trailing data without a line-ending (or the chunk was loaded under a non-standard -policy setting)
#[para]Whilst such data may not conform to definitions (e.g POSIX) of the terms 'textfile' and 'line' - it is useful here to represent it as a line with metadata le set to "none" #[para]Whilst such data may not conform to definitions (e.g POSIX) of the terms 'textfile' and 'line' - it is useful here to represent it as a line with metadata le set to "none"
#[para]To return just the data which might more commonly be needed for dealing with lines, use the [method linepayload] method - which returns the line data minus line-ending #[para]To return just the data which might more commonly be needed for dealing with lines, use the [method linepayload] method - which returns the line data minus line-ending
lassign [my numeric_linerange $lineindex 0] lineindex lassign [my numeric_linerange $lineindex 0] lineindex
set le [dict get $o_linemap $lineindex le] set le [dict get $o_linemap $lineindex le]
set le_chars [dict get [dict create lf \n crlf \r\n none ""] $le] set le_chars [dict get [dict create lf \n crlf \r\n none ""] $le]
@ -642,13 +641,13 @@ namespace eval punk::fileline::class {
set opt_strategy [dict get $opts -strategy] set opt_strategy [dict get $opts -strategy]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_start [dict get $opts -start] set opt_start [dict get $opts -start]
set opt_start [expr {$opt_start}] set opt_start [expr {$opt_start}]
if {$opt_start != 0} {error "-start unimplemented"} if {$opt_start != 0} {error "-start unimplemented"}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_end [dict get $opts -end] set opt_end [dict get $opts -end]
set max_line_index [expr {[llength $o_payloadlist]-1}] set max_line_index [expr {[llength $o_payloadlist]-1}]
if {$opt_end eq "end"} { if {$opt_end eq "end"} {
set opt_end $max_line_index set opt_end $max_line_index
} }
#TODO #TODO
if {$opt_end < $max_line_index} {error "-end less than max_line_index unimplemented"} if {$opt_end < $max_line_index} {error "-end less than max_line_index unimplemented"}
@ -706,7 +705,7 @@ namespace eval punk::fileline::class {
#[para]Line Metadata such as the line-ending for a particular line and the byte/character range it occupies within the chunk can be retrieved with the [method linemeta] method #[para]Line Metadata such as the line-ending for a particular line and the byte/character range it occupies within the chunk can be retrieved with the [method linemeta] method
#[para]To retrieve both the line text and metadata in a single call the [method lineinfo] method can be used #[para]To retrieve both the line text and metadata in a single call the [method lineinfo] method can be used
#[para]To retrieve an entire line including line-ending use the [method line] method. #[para]To retrieve an entire line including line-ending use the [method line] method.
lassign [my numeric_linerange $lineindex 0] lineindex lassign [my numeric_linerange $lineindex 0] lineindex
return [lindex $o_payloadlist $lineindex] return [lindex $o_payloadlist $lineindex]
} }
method linepayloads {startindex endindex} { method linepayloads {startindex endindex} {
@ -723,17 +722,17 @@ namespace eval punk::fileline::class {
#[list_begin itemized] #[list_begin itemized]
#[item] le #[item] le
#[para] A string representing the type of line-ending: crlf|lf|none #[para] A string representing the type of line-ending: crlf|lf|none
#[item] linelen #[item] linelen
#[para] The number of characters/bytes in the whole line including line-ending if any #[para] The number of characters/bytes in the whole line including line-ending if any
#[item] payloadlen #[item] payloadlen
#[para] The number of character/bytes in the line excluding line-ending #[para] The number of character/bytes in the line excluding line-ending
#[item] start #[item] start
#[para] The zero-based index into the associated raw file data indicating at which byte/character index this line begins #[para] The zero-based index into the associated raw file data indicating at which byte/character index this line begins
#[item] end #[item] end
#[para] The zero-based index into the associated raw file data indicating at which byte/character index this line ends #[para] The zero-based index into the associated raw file data indicating at which byte/character index this line ends
#[para] This end-point corresponds to the last character of the line-ending if any - not necessarily the last character of the line's payload #[para] This end-point corresponds to the last character of the line-ending if any - not necessarily the last character of the line's payload
#[list_end] #[list_end]
lassign [my numeric_linerange $lineindex 0] lineindex lassign [my numeric_linerange $lineindex 0] lineindex
dict get $o_linemap $lineindex dict get $o_linemap $lineindex
} }
method lineinfo {lineindex} { method lineinfo {lineindex} {
@ -798,7 +797,7 @@ namespace eval punk::fileline::class {
method chunkrange_to_linerange {chunkstart chunkend} { method chunkrange_to_linerange {chunkstart chunkend} {
#*** !doctools #*** !doctools
#[call class::textinfo [method chunkrange_to_linerange] [arg chunkstart] [arg chunkend]] #[call class::textinfo [method chunkrange_to_linerange] [arg chunkstart] [arg chunkend]]
lassign [my numeric_chunkrange $chunkstart $chunkend] chunkstart chunkend lassign [my numeric_chunkrange $chunkstart $chunkend] chunkstart chunkend
set linestart -1 set linestart -1
for {set i 0} {$i < [llength $o_payloadlist]} {incr i} { for {set i 0} {$i < [llength $o_payloadlist]} {incr i} {
@ -830,7 +829,7 @@ namespace eval punk::fileline::class {
#[para]truncation shows the shortened (missing bytes on left and/or right side) part of the entire line (potentially including line-ending or even partial line-ending) #[para]truncation shows the shortened (missing bytes on left and/or right side) part of the entire line (potentially including line-ending or even partial line-ending)
#[para]Note that this truncation info is only in the return value of this method - and will not be reflected in [method lineinfo] queries to the main chunk. #[para]Note that this truncation info is only in the return value of this method - and will not be reflected in [method lineinfo] queries to the main chunk.
lassign [my numeric_chunkrange $chunkstart $chunkend] chunkstart chunkend lassign [my numeric_chunkrange $chunkstart $chunkend] chunkstart chunkend
set defaults [dict create\ set defaults [dict create\
-show_truncated 0\ -show_truncated 0\
] ]
@ -841,9 +840,9 @@ namespace eval punk::fileline::class {
} }
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
set opt_show_truncated [dict get $opts -show_truncated] set opt_show_truncated [dict get $opts -show_truncated]
# -- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- ---
set infolist [list] set infolist [list]
set linerange [my chunkrange_to_linerange $chunkstart $chunkend] set linerange [my chunkrange_to_linerange $chunkstart $chunkend]
@ -879,8 +878,8 @@ namespace eval punk::fileline::class {
set truncated [string range $payload_and_le $split end] set truncated [string range $payload_and_le $split end]
set lhs [string range $payload_and_le 0 $split-1] set lhs [string range $payload_and_le 0 $split-1]
dict set first truncated $truncated dict set first truncated $truncated
dict set first truncatedleft $lhs dict set first truncatedleft $lhs
} }
} }
########################### ###########################
@ -909,7 +908,7 @@ namespace eval punk::fileline::class {
if {$chunkend < [dict get $end_info end]} { if {$chunkend < [dict get $end_info end]} {
#there is rhs truncation #there is rhs truncation
if {[dict get $first is_truncated]} { if {[dict get $first is_truncated]} {
dict set first truncatedside [list left right] dict set first truncatedside [list left right]
} else { } else {
dict set first is_truncated 1 dict set first is_truncated 1
dict set first truncatedside [list right] dict set first truncatedside [list right]
@ -926,7 +925,7 @@ namespace eval punk::fileline::class {
set le_chars [dict get [dict create lf \n crlf \r\n none ""] [dict get $end_info le]] set le_chars [dict get [dict create lf \n crlf \r\n none ""] [dict get $end_info le]]
set payload_and_le "${payload}${le_chars}" set payload_and_le "${payload}${le_chars}"
set split [expr {$chunkend - $line_start}] set split [expr {$chunkend - $line_start}]
set truncated [string range $payload_and_le 0 $split] set truncated [string range $payload_and_le 0 $split]
set rhs [string range $payload_and_le $split+1 end] set rhs [string range $payload_and_le $split+1 end]
dict set first truncatedright $rhs dict set first truncatedright $rhs
if {"left" ni [dict get $first truncatedside]} { if {"left" ni [dict get $first truncatedside]} {
@ -972,13 +971,13 @@ namespace eval punk::fileline::class {
set payload_and_le "${payload}${le_chars}" set payload_and_le "${payload}${le_chars}"
set split [expr {$chunkend - $line_start}] set split [expr {$chunkend - $line_start}]
set truncated [string range $payload_and_le 0 $split] set truncated [string range $payload_and_le 0 $split]
set rhs [string range $payload_and_le $split+1 end] set rhs [string range $payload_and_le $split+1 end]
dict set last truncated $truncated dict set last truncated $truncated
dict set last truncatedright $rhs dict set last truncatedright $rhs
#this has the effect that truncating the rhs by 1 can result in truncated being larger than original payload for crlf lines - as payload now sees the cr #this has the effect that truncating the rhs by 1 can result in truncated being larger than original payload for crlf lines - as payload now sees the cr
#this is a bit unintuitive - but probably best reflects the reality. The truncated value is the truncated 'line' rather than the truncated 'payload' #this is a bit unintuitive - but probably best reflects the reality. The truncated value is the truncated 'line' rather than the truncated 'payload'
} }
} }
@ -992,7 +991,7 @@ namespace eval punk::fileline::class {
########################### ###########################
#assertion all records have is_truncated key. #assertion all records have is_truncated key.
#assertion if is_truncated == 1 truncatedside should contain a list of either left, right or both left and right #assertion if is_truncated == 1 truncatedside should contain a list of either left, right or both left and right
#assertion If not opt_show_truncated - then truncated records will not have truncated,truncatedleft,truncatedright keys. #assertion If not opt_show_truncated - then truncated records will not have truncated,truncatedleft,truncatedright keys.
return $infolist return $infolist
} }
@ -1018,12 +1017,12 @@ namespace eval punk::fileline::class {
#Also check if the truncation is directly between an crlf #Also check if the truncation is directly between an crlf
#both an lhs split and an rhs split could land between cr and lf #both an lhs split and an rhs split could land between cr and lf
#to be precise - we should presumably count the part within our chunk as either a none for cr or an lf #to be precise - we should presumably count the part within our chunk as either a none for cr or an lf
#This means a caller counting chunk by chunk using this method will sometimes get the wrong answer depending on where crlfs lie relative to their chosen chunk size #This means a caller counting chunk by chunk using this method will sometimes get the wrong answer depending on where crlfs lie relative to their chosen chunk size
#This is presumably ok - as it should be a well known thing to watch out for. #This is presumably ok - as it should be a well known thing to watch out for.
#If we're only receiving chunk by chunk we can't reliably detect splits vs lone <cr>s in the data #If we're only receiving chunk by chunk we can't reliably detect splits vs lone <cr>s in the data
#There are surely more efficient ways for a caller to count line-endings in the way that makes sense for them #There are surely more efficient ways for a caller to count line-endings in the way that makes sense for them
#but we should makes things as easy as possible for users of this line/chunk structure anyway. #but we should makes things as easy as possible for users of this line/chunk structure anyway.
set first [lindex $infolines 0] set first [lindex $infolines 0]
if {[dict get $first is_truncated]} { if {[dict get $first is_truncated]} {
#could be the only line - and truncated at one or both ends. #could be the only line - and truncated at one or both ends.
@ -1036,7 +1035,7 @@ namespace eval punk::fileline::class {
#if so - then split can only be left side #if so - then split can only be left side
} }
return [dict create lf $lf_count crlf $crlf_count unterminated $none_count warning line_ending_splits_unimplemented] return [dict create lf $lf_count crlf $crlf_count unterminated $none_count warning line_ending_splits_unimplemented]
} }
@ -1062,13 +1061,13 @@ namespace eval punk::fileline::class {
method normalize_indices {startidx endidx max} { method normalize_indices {startidx endidx max} {
#*** !doctools #*** !doctools
#[call class::textinfo [method normalize_indices] [arg startidx] [arg endidx] [arg max]] #[call class::textinfo [method normalize_indices] [arg startidx] [arg endidx] [arg max]]
#[para]A utility to convert some of the of Tcl-style list-index expressions such as end, end-1 etc to valid indices in the range 0 to the supplied max #[para]A utility to convert some of the of Tcl-style list-index expressions such as end, end-1 etc to valid indices in the range 0 to the supplied max
#[para]Basic addition and subtraction expressions such as 4-1 5+2 are accepted #[para]Basic addition and subtraction expressions such as 4-1 5+2 are accepted
#[para]startidx higher than endidx is allowed #[para]startidx higher than endidx is allowed
#[para]Unlike Tcl's index expressions - we raise an error if the calculated index is out of bounds 0 to max #[para]Unlike Tcl's index expressions - we raise an error if the calculated index is out of bounds 0 to max
set original_startidx $startidx set original_startidx $startidx
set original_endidx $endidx set original_endidx $endidx
set startidx [string map [list _ ""] $startidx] ;#don't barf on Tcl 8.7+ underscores in numbers - we can't just use expr because it will not handle end-x set startidx [string map [list _ ""] $startidx] ;#don't barf on Tcl 8.7+ underscores in numbers - we can't just use expr because it will not handle end-x
set endidx [string map [list _ ""] $endidx] set endidx [string map [list _ ""] $endidx]
if {![string is digit -strict "$startidx$endidx"]} { if {![string is digit -strict "$startidx$endidx"]} {
foreach whichvar [list start end] { foreach whichvar [list start end] {
@ -1079,9 +1078,9 @@ namespace eval punk::fileline::class {
set index $max set index $max
} }
"*-*" { "*-*" {
#end-int or int-int - like lrange etc we don't accept arbitrarily complex expressions #end-int or int-int - like lrange etc we don't accept arbitrarily complex expressions
lassign [split $index -] A B lassign [split $index -] A B
if {$A eq "end"} { if {$A eq "end"} {
set index [expr {$max - $B}] set index [expr {$max - $B}]
} else { } else {
set index [expr {$A - $B}] set index [expr {$A - $B}]
@ -1089,7 +1088,7 @@ namespace eval punk::fileline::class {
} }
"*+*" { "*+*" {
lassign [split $index +] A B lassign [split $index +] A B
if {$A eq "end"} { if {$A eq "end"} {
#review - this will just result in out of bounds error in final test - as desired #review - this will just result in out of bounds error in final test - as desired
#By calculating here - we will see the result in the error message - but it's probably not particularly useful - as we don't really need end+ support at all. #By calculating here - we will see the result in the error message - but it's probably not particularly useful - as we don't really need end+ support at all.
set index [expr {$max + $B}] set index [expr {$max + $B}]
@ -1099,9 +1098,9 @@ namespace eval punk::fileline::class {
} }
default { default {
#May be something like +2 or -0 which braced expr can hanle #May be something like +2 or -0 which braced expr can hanle
#we would like to avoid unbraced expr here - as we're potentially dealing with ranges that may come from external sources. #we would like to avoid unbraced expr here - as we're potentially dealing with ranges that may come from external sources.
if {[catch {expr {$index}} index]} { if {[catch {expr {$index}} index]} {
#could be end+x - but we don't want out of bounds to be valid #could be end+x - but we don't want out of bounds to be valid
#set it to something that the final bounds expr test can deal with #set it to something that the final bounds expr test can deal with
set index Inf set index Inf
} }
@ -1110,13 +1109,13 @@ namespace eval punk::fileline::class {
} }
} }
} }
#Unlike Tcl lrange,lindex etc - we don't want to support out of bound indices. #Unlike Tcl lrange,lindex etc - we don't want to support out of bound indices.
#show the supplied index and what it was mapped to in the error message. #show the supplied index and what it was mapped to in the error message.
if {$startidx < 0 || $startidx > $max} { if {$startidx < 0 || $startidx > $max} {
error "Bad start index '$original_startidx'. $startidx out of bounds 0 - $max" error "Bad start index '$original_startidx'. $startidx out of bounds 0 - $max"
} }
if {$endidx < 0 || $endidx > $max} { if {$endidx < 0 || $endidx > $max} {
error "Bad end index '$original_endidx'. $endidx out of bounds 0 - $max (try $max or end)" error "Bad end index '$original_endidx'. $endidx out of bounds 0 - $max (try $max or end)"
} }
return [list $startidx $endidx] return [list $startidx $endidx]
} }
@ -1137,7 +1136,7 @@ namespace eval punk::fileline::class {
set crlf_replace [list \r\n $o_CRLF_C \n $o_LF_C] set crlf_replace [list \r\n $o_CRLF_C \n $o_LF_C]
set normalised_data [string map $crlf_replace $o_chunk] set normalised_data [string map $crlf_replace $o_chunk]
set lf_lines [split $normalised_data $o_LF_C] set lf_lines [split $normalised_data $o_LF_C]
set idx 0 set idx 0
set lf_count 0 set lf_count 0
@ -1146,14 +1145,14 @@ namespace eval punk::fileline::class {
set i 0 set i 0
set imax [expr {[llength $lf_lines]-1}] set imax [expr {[llength $lf_lines]-1}]
foreach lfln $lf_lines { foreach lfln $lf_lines {
set crlf_parts [split $lfln $o_CRLF_C] set crlf_parts [split $lfln $o_CRLF_C]
if {[llength $crlf_parts] <= 1} { if {[llength $crlf_parts] <= 1} {
#no crlf #no crlf
set payloadlen [string length $lfln] set payloadlen [string length $lfln]
set le_size 1 set le_size 1
set le lf set le lf
if {$i == $imax} { if {$i == $imax} {
#no more lf segments - and no crlfs #no more lf segments - and no crlfs
if {$payloadlen > 0} { if {$payloadlen > 0} {
#last line in split has chars - therefore there was no trailing line-ending #last line in split has chars - therefore there was no trailing line-ending
set le_size 0 set le_size 0
@ -1178,7 +1177,7 @@ namespace eval punk::fileline::class {
set payloadlen [string length $crlfpart] set payloadlen [string length $crlfpart]
set linelen [expr {$payloadlen + 2}] set linelen [expr {$payloadlen + 2}]
dict set o_linemap $idx [list le crlf linelen $linelen payloadlen $payloadlen start $filedata_offset end [expr {$filedata_offset + $linelen -1}]] dict set o_linemap $idx [list le crlf linelen $linelen payloadlen $payloadlen start $filedata_offset end [expr {$filedata_offset + $linelen -1}]]
incr filedata_offset $linelen incr filedata_offset $linelen
incr crlf_count incr crlf_count
incr idx incr idx
} }
@ -1201,7 +1200,7 @@ namespace eval punk::fileline::class {
set le lf set le lf
} }
lappend o_payloadlist $lfpart lappend o_payloadlist $lfpart
set linelen [expr {$payloadlen + $le_size}] set linelen [expr {$payloadlen + $le_size}]
dict set o_linemap $idx [list le $le linelen $linelen payloadlen $payloadlen start $filedata_offset end [expr {$filedata_offset + $linelen -1}]] dict set o_linemap $idx [list le $le linelen $linelen payloadlen $payloadlen start $filedata_offset end [expr {$filedata_offset + $linelen -1}]]
incr filedata_offset $linelen incr filedata_offset $linelen
@ -1222,8 +1221,11 @@ namespace eval punk::fileline::class {
#o_linemap #o_linemap
set oldsize [string length $o_chunk] set oldsize [string length $o_chunk]
set newchunk "" set newchunk ""
#review - what was the intention here?
puts stderr "regenerate_chunk -warning code incomplete"
dict for {idx lineinfo} $o_linemap { dict for {idx lineinfo} $o_linemap {
set #???
#set
} }
@ -1249,9 +1251,19 @@ namespace eval punk::fileline {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::fileline}] #[subsection {Namespace punk::fileline}]
#[para] Core API functions for punk::fileline #[para] Core API functions for punk::fileline
#[list_begin definitions] #[list_begin definitions]
punk::args::define {
@id -id ::punk::fileline::get_textinfo
@cmd -name punk::fileline::get_textinfo -help\
"return: textinfo object instance"
-file -default {} -type existingfile
-translation -default iso8859-1
-encoding -default "\uFFFF"
-includebom -default 0
@values -min 0 -max 1
}
proc get_textinfo {args} { proc get_textinfo {args} {
#*** !doctools #*** !doctools
#[call get_textinfo [opt {option value...}] [opt datachunk]] #[call get_textinfo [opt {option value...}] [opt datachunk]]
@ -1263,18 +1275,11 @@ namespace eval punk::fileline {
#[para]If -includebom 1 is specified - the bom will be retained in the stored chunk and the data for line 1, but will undergo the same encoding transformation as the rest of the data #[para]If -includebom 1 is specified - the bom will be retained in the stored chunk and the data for line 1, but will undergo the same encoding transformation as the rest of the data
#[para]The get_bomid method of the returned object will contain an identifier for any BOM encountered. #[para]The get_bomid method of the returned object will contain an identifier for any BOM encountered.
#[para] e.g utf-8,utf-16be, utf-16le, utf-32be, utf32-le, SCSU, BOCU-1,GB18030, UTF-EBCDIC, utf-1, utf-7 #[para] e.g utf-8,utf-16be, utf-16le, utf-32be, utf32-le, SCSU, BOCU-1,GB18030, UTF-EBCDIC, utf-1, utf-7
#[para]If the encoding specified in the BOM isn't recognised by Tcl - the resulting data is likely to remain as the raw bytes of whatever encoding that is. #[para]If the encoding specified in the BOM isn't recognised by Tcl - the resulting data is likely to remain as the raw bytes of whatever encoding that is.
#[para]Currently only utf-8, utf-16* and utf-32* are properly supported even though the other BOMs are detected, reported via get_bomid, and stripped from the data. #[para]Currently only utf-8, utf-16* and utf-32* are properly supported even though the other BOMs are detected, reported via get_bomid, and stripped from the data.
#[para]GB18030 falls back to cp936/gbk (unless a gb18030 encoding has been installed). Use -encoding iso8859-1 if this isn't suitable and you need to do your own processing of the bytes. #[para]GB18030 falls back to cp936/gbk (unless a gb18030 encoding has been installed). Use -encoding iso8859-1 if this isn't suitable and you need to do your own processing of the bytes.
set argument_specification { lassign [dict values [punk::args::get_by_id ::punk::fileline::get_textinfo $args]] opts values
-file -default {} -type existingfile
-translation -default iso8859-1
-encoding -default "\uFFFF"
-includebom -default 0
*values -min 0 -max 1
}
lassign [dict values [punk::args::get_dict $argument_specification $args]] opts values
# -- --- --- --- # -- --- --- ---
set opt_file [dict get $opts -file] set opt_file [dict get $opts -file]
set opt_translation [dict get $opts -translation] set opt_translation [dict get $opts -translation]
@ -1283,10 +1288,10 @@ namespace eval punk::fileline {
# -- --- --- --- # -- --- --- ---
if {$opt_file ne ""} { if {$opt_file ne ""} {
set filename $opt_file set filename $opt_file
set fd [open $filename r] set fd [open $filename r]
fconfigure $fd -translation binary -encoding $opt_translation;#should use translation binary to get actual line-endings - but we allow caller to override chan configure $fd -translation binary -encoding $opt_translation;#should use translation binary to get actual line-endings - but we allow caller to override
#Always read encoding in binary - check for bom below and/or apply chosen opt_encoding #Always read encoding in binary - check for bom below and/or apply chosen opt_encoding
set rawchunk [read $fd] set rawchunk [read $fd]
close $fd close $fd
if {[llength $values]} { if {[llength $values]} {
@ -1333,7 +1338,7 @@ namespace eval punk::fileline {
set is_reliabletxt 1 set is_reliabletxt 1
set startdata 4 set startdata 4
} elseif {$maybe_bom eq "fffe0000"} { } elseif {$maybe_bom eq "fffe0000"} {
#Technically ambiguous - could be utf-16le bom followed by utf-16 null character (2 byte null) #Technically ambiguous - could be utf-16le bom followed by utf-16 null character (2 byte null)
puts stderr "WARNING - ambiguous BOM fffe0000 found. Treating as utf-32le - but could be utf-16le - consider manually setting -encoding or converting data to another encoding." puts stderr "WARNING - ambiguous BOM fffe0000 found. Treating as utf-32le - but could be utf-16le - consider manually setting -encoding or converting data to another encoding."
set bomid utf-32le set bomid utf-32le
set bomenc utf-32le set bomenc utf-32le
@ -1358,7 +1363,7 @@ namespace eval punk::fileline {
set bomenc "binary" ;# utf-8??? set bomenc "binary" ;# utf-8???
set startdata 3 set startdata 3
} elseif {$maybe_bom eq "84319533"} { } elseif {$maybe_bom eq "84319533"} {
if {![dict exists [punk::char::page_names_dict gb18030]]} { if {![dict exists [punk::char::page_names_dict gb18030] gb18030]} {
puts stderr "WARNING - no direct support for GB18030 (chinese) - falling back to cp936/gbk" puts stderr "WARNING - no direct support for GB18030 (chinese) - falling back to cp936/gbk"
set bomenc cp936 set bomenc cp936
} else { } else {
@ -1372,7 +1377,7 @@ namespace eval punk::fileline {
set bomenc binary set bomenc binary
set startdata 3 set startdata 3
} elseif {[string match "2b2f76*" $maybe_bom]} { } elseif {[string match "2b2f76*" $maybe_bom]} {
puts stderr "WARNING utf-7 BOM 2b2f76 found - not supported. Falling back to binary and leaving BOM in data!" puts stderr "WARNING utf-7 BOM 2b2f76 found - not supported. Falling back to binary and leaving BOM in data!"
#review - work out how to strip bom - last 2 bits of 4th byte belong to following character #review - work out how to strip bom - last 2 bits of 4th byte belong to following character
set bomid utf-7 set bomid utf-7
set bomenc binary set bomenc binary
@ -1431,7 +1436,7 @@ namespace eval punk::fileline {
} else { } else {
set datachunk [encoding convertfrom $bomenc [string range $rawchunk $startdata end]] set datachunk [encoding convertfrom $bomenc [string range $rawchunk $startdata end]]
set encoding_selected $bomenc set encoding_selected $bomenc
} }
} else { } else {
#tcl 8.7 plus has utf-16le etc #tcl 8.7 plus has utf-16le etc
set datachunk [encoding convertfrom $bomenc [string range $rawchunk $startdata end]] set datachunk [encoding convertfrom $bomenc [string range $rawchunk $startdata end]]
@ -1441,7 +1446,7 @@ namespace eval punk::fileline {
#!? #!?
if {$bomenc eq "binary"} { if {$bomenc eq "binary"} {
set datachunk [string range $rawchunk $startdata end] set datachunk [string range $rawchunk $startdata end]
set encoding_selected binary set encoding_selected binary
} else { } else {
set datachunk [encoding convertfrom utf-8 [string range $rawchunk $startdata end]] set datachunk [encoding convertfrom utf-8 [string range $rawchunk $startdata end]]
set encoding_selected utf-8 set encoding_selected utf-8
@ -1483,7 +1488,7 @@ namespace eval punk::fileline {
proc file_boundary_display {filename startbyte endbyte chunksize args} { proc file_boundary_display {filename startbyte endbyte chunksize args} {
set fd [open $filename r] ;#use default error if file not readable set fd [open $filename r] ;#use default error if file not readable
fconfigure $fd -translation binary chan configure $fd -translation binary
set rawfiledata [read $fd] set rawfiledata [read $fd]
close $fd close $fd
set textobj [class::textinfo new $rawfiledata] set textobj [class::textinfo new $rawfiledata]
@ -1508,7 +1513,7 @@ namespace eval punk::fileline::lib {
namespace path [namespace parent] namespace path [namespace parent]
#*** !doctools #*** !doctools
#[subsection {Namespace punk::fileline::lib}] #[subsection {Namespace punk::fileline::lib}]
#[para] Secondary functions that are part of the API #[para] Secondary functions that are part of the API
#[list_begin definitions] #[list_begin definitions]
@ -1530,12 +1535,12 @@ namespace eval punk::fileline::lib {
#[para]e.g #[para]e.g
#[example_begin] #[example_begin]
# range_spans_chunk_boundaries 10 1750 512 # range_spans_chunk_boundaries 10 1750 512
# is_span 1 boundaries {512 1024 1536} # is_span 1 boundaries {512 1024 1536}
#[example_end] #[example_end]
#[para]The -offset <int> option #[para]The -offset <int> option
#[example_begin] #[example_begin]
# range_spans_chunk_boundaries 10 1750 512 -offset 2 # range_spans_chunk_boundaries 10 1750 512 -offset 2
# is_span 1 boundaries {514 1026 1538} # is_span 1 boundaries {514 1026 1538}
#[example_end] #[example_end]
#[para] This function automatically uses lseq (if Tcl >= 8.7) when number of boundaries spanned is approximately greater than 75 #[para] This function automatically uses lseq (if Tcl >= 8.7) when number of boundaries spanned is approximately greater than 75
if {[catch {package require Tcl 8.7-}]} { if {[catch {package require Tcl 8.7-}]} {
@ -1557,7 +1562,7 @@ namespace eval punk::fileline::lib {
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
-offset -default 0 -offset -default 0
} $args] } $args]
lassign [dict values $argd] opts remainingargs lassign [dict values $argd] leaders opts remainingargs
} }
@ -1574,12 +1579,12 @@ namespace eval punk::fileline::lib {
namespace eval punk::fileline::system { namespace eval punk::fileline::system {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::fileline::system}] #[subsection {Namespace punk::fileline::system}]
#[para] Internal functions that are not part of the API #[para] Internal functions that are not part of the API
proc wordswap16 {data} { proc wordswap16 {data} {
#scan in one endianness - format in the other. Whether we scan le/be first doesn't matter as long as we format using the opposite endianness #scan in one endianness - format in the other. Whether we scan le/be first doesn't matter as long as we format using the opposite endianness
binary scan $data s* elements ;#scan little endian binary scan $data s* elements ;#scan little endian
return [binary format S* $elements] ;#format big endian return [binary format S* $elements] ;#format big endian
} }
proc wordswap32 {data} { proc wordswap32 {data} {
binary scan $data i* elements binary scan $data i* elements
@ -1620,7 +1625,7 @@ namespace eval punk::fileline::system {
set start [expr {$start + ($chunksize - $smod)}] set start [expr {$start + ($chunksize - $smod)}]
if {$start > $end} { if {$start > $end} {
return [list is_span 0 boundaries {}] return [list is_span 0 boundaries {}]
} }
} }
set boundaries [lseq $start to $end $chunksize] set boundaries [lseq $start to $end $chunksize]
#offset can be negative #offset can be negative
@ -1630,7 +1635,7 @@ namespace eval punk::fileline::system {
} else { } else {
set overflow 0 set overflow 0
} }
set boundaries [lmap v $boundaries[unset boundaries] {expr {$v + $opt_offset}}] set boundaries [lmap v $boundaries[unset boundaries] {expr {$v + $opt_offset}}]
if {$overflow} { if {$overflow} {
#we don't know how many overflowed.. #we don't know how many overflowed..
set inrange [list] set inrange [list]
@ -1666,7 +1671,7 @@ namespace eval punk::fileline::system {
set opt_offset [dict get $opts -offset] set opt_offset [dict get $opts -offset]
# -- --- --- --- # -- --- --- ---
set is_span 0 set is_span 0
set smod [expr {$start % $chunksize}] set smod [expr {$start % $chunksize}]
if {$smod != 0} { if {$smod != 0} {
set start [expr {$start + ($chunksize - $smod)}] set start [expr {$start + ($chunksize - $smod)}]
@ -1679,7 +1684,7 @@ namespace eval punk::fileline::system {
set btrack $bstart set btrack $bstart
set boff [expr {$btrack + $opt_offset}] ;#must be growing even if start and offset are negative - as chunksize is at least 1 set boff [expr {$btrack + $opt_offset}] ;#must be growing even if start and offset are negative - as chunksize is at least 1
while {$boff < $start} { while {$boff < $start} {
incr btrack $chunksize incr btrack $chunksize
set boff [expr {$btrack + $opt_offset}] set boff [expr {$btrack + $opt_offset}]
} }
set bstart $btrack set bstart $btrack
@ -1687,9 +1692,9 @@ namespace eval punk::fileline::system {
set bstart $start set bstart $start
} }
for {set b $bstart} {[set boff [expr {$b + $opt_offset}]] <= $end} {incr b $chunksize} { for {set b $bstart} {[set boff [expr {$b + $opt_offset}]] <= $end} {incr b $chunksize} {
lappend boundaries $boff lappend boundaries $boff
} }
return [list is_span [expr {[llength $boundaries]>0}] boundaries $boundaries offset $opt_offset] return [list is_span [expr {[llength $boundaries]>0}] boundaries $boundaries offset $opt_offset]
} }
@ -1705,7 +1710,7 @@ namespace eval punk::fileline::ansi {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::fileline::ansi}] #[subsection {Namespace punk::fileline::ansi}]
#[para]These are ansi functions imported from punk::ansi - or no-ops if that package is unavailable #[para]These are ansi functions imported from punk::ansi - or no-ops if that package is unavailable
#[para]See [package punk::ansi] for documentation #[para]See [package punk::ansi] for documentation
#[list_begin definitions] #[list_begin definitions]
variable enabled 1 variable enabled 1
#*** !doctools #*** !doctools
@ -1718,11 +1723,11 @@ namespace eval punk::fileline::ansi {
} }
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::fileline [namespace eval punk::fileline { package provide punk::fileline [namespace eval punk::fileline {
variable pkg punk::fileline variable pkg punk::fileline
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

2401
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm vendored

File diff suppressed because it is too large Load Diff

172
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm vendored

@ -4,6 +4,7 @@ package provide punk::mix::base [namespace eval punk::mix::base {
}] }]
package require punk::path package require punk::path
package require punk::lib ;#format_number etc
#base internal plumbing functions #base internal plumbing functions
namespace eval punk::mix::base { namespace eval punk::mix::base {
@ -17,7 +18,7 @@ namespace eval punk::mix::base {
set extension "" set extension ""
} }
#--------- #---------
uplevel #0 [list interp alias {} $cmdname {} punk::mix::base::_cli -extension $extension] uplevel #0 [list interp alias {} $cmdname {} punk::mix::base::_cli -extension $extension]
} }
proc _cli {args} { proc _cli {args} {
@ -35,12 +36,14 @@ namespace eval punk::mix::base {
} }
#puts stderr "punk::mix::base extension: [string trimleft $extension :]" #puts stderr "punk::mix::base extension: [string trimleft $extension :]"
if {![string length $extension]} { if {![string length $extension]} {
#if still no extension - must have been called dirctly as punk::mix::base::_cli #if still no extension - must have been called directly as punk::mix::base::_cli
if {![llength $args]} { if {![llength $args]} {
set args "help" set args "help"
} }
set extension [namespace current] set extension [namespace current]
} }
#init usually used to load commandsets (and export their names) into the extension namespace/ensemble
${extension}::_init
if {![llength $args]} { if {![llength $args]} {
if {[info exists ${extension}::default_command]} { if {[info exists ${extension}::default_command]} {
tailcall $extension [set ${extension}::default_command] tailcall $extension [set ${extension}::default_command]
@ -66,7 +69,7 @@ namespace eval punk::mix::base {
} }
#puts stderr "arglen:[llength $args]" #puts stderr "arglen:[llength $args]"
#puts stdout "_unknown '$ns' '$args'" #puts stdout "_unknown '$ns' '$args'"
set d_commands [get_commands -extension $extension] set d_commands [get_commands -extension $extension]
set all_commands [list {*}[dict get $d_commands main] {*}[dict get $d_commands base]] set all_commands [list {*}[dict get $d_commands main] {*}[dict get $d_commands base]]
@ -95,11 +98,11 @@ namespace eval punk::mix::base {
} }
tailcall [namespace current] $subcommand {*}$argvals {*}$args -extension $from_ns tailcall [namespace current] $subcommand {*}$argvals {*}$args -extension $from_ns
} else { } else {
if {[regexp {.*[*?].*} $subcommand]} { if {[regexp {.*[*?].*} $subcommand]} {
set d_commands [get_commands -extension $from_ns] set d_commands [get_commands -extension $from_ns]
set all_commands [list {*}[dict get $d_commands main] {*}[dict get $d_commands base]] set all_commands [list {*}[dict get $d_commands main] {*}[dict get $d_commands base]]
set matched_commands [lsearch -all -inline $all_commands $subcommand] set matched_commands [lsearch -all -inline $all_commands $subcommand]
set commands "" set commands ""
foreach m $matched_commands { foreach m $matched_commands {
append commands $m \n append commands $m \n
} }
@ -110,12 +113,12 @@ namespace eval punk::mix::base {
} }
proc _split_args {arglist} { proc _split_args {arglist} {
#don't assume arglist is fully paired. #don't assume arglist is fully paired.
set posn [lsearch $arglist -extension] set posn [lsearch $arglist -extension]
set opts [list] set opts [list]
if {$posn >= 0} { if {$posn >= 0} {
if {$posn+2 <= [llength $arglist]} { if {$posn+2 <= [llength $arglist]} {
set opts [list -extension [lindex $arglist $posn+1]] set opts [list -extension [lindex $arglist $posn+1]]
set argsremaining [lreplace $arglist $posn $posn+1] set argsremaining [lreplace $arglist $posn $posn+1]
} else { } else {
#no value supplied to -extension #no value supplied to -extension
error "punk::mix::base::_split_args - no value found for option '-extension'. Supply a value or omit the option." error "punk::mix::base::_split_args - no value found for option '-extension'. Supply a value or omit the option."
@ -148,7 +151,7 @@ namespace eval punk::mix::base {
if {![string length $extension]} { if {![string length $extension]} {
set extension [namespace qualifiers [lindex [info level -1] 0]] set extension [namespace qualifiers [lindex [info level -1] 0]]
} }
set maincommands [list] set maincommands [list]
#extension may still be blank e.g if punk::mix::base::get_commands called directly #extension may still be blank e.g if punk::mix::base::get_commands called directly
if {[string length $extension]} { if {[string length $extension]} {
@ -161,7 +164,7 @@ namespace eval punk::mix::base {
} }
foreach c $nscommands { foreach c $nscommands {
set cmd [namespace tail $c] set cmd [namespace tail $c]
lappend maincommands $cmd lappend maincommands $cmd
} }
set maincommands [lsort $maincommands] set maincommands [lsort $maincommands]
} }
@ -187,29 +190,29 @@ namespace eval punk::mix::base {
set basecommands [lsort $basecommands] set basecommands [lsort $basecommands]
return [list main $maincommands base $basecommands] return [list main $maincommands base $basecommands]
} }
proc help {args} { proc help {args} {
#' **%ensemblecommand% help** *args* #' **%ensemblecommand% help** *args*
#' #'
#' Help for ensemble commands in the command line interface #' Help for ensemble commands in the command line interface
#' #'
#' #'
#' Arguments: #' Arguments:
#' #'
#' * args - first word of args is the helptopic requested - usually a command name #' * args - first word of args is the helptopic requested - usually a command name
#' - calling help with no arguments will list available commands #' - calling help with no arguments will list available commands
#' #'
#' Returns: help text (text) #' Returns: help text (text)
#' #'
#' Examples: #' Examples:
#' #'
#' ``` #' ```
#' %ensemblecommand% help <commandname> #' %ensemblecommand% help <commandname>
#' ``` #' ```
#' #'
#' #'
#extension.= @@opts/@?@-extension,args@@args=>. [_split_args $args] {| #extension.= @@opts/@?@-extension,args@@args=>. [_split_args $args] {|
# >} inspect -label a {| # >} inspect -label a {|
@ -217,7 +220,7 @@ namespace eval punk::mix::base {
# pipecase ,0/1/#= $switchargs {| # pipecase ,0/1/#= $switchargs {|
# e/0 # e/0
# >} .=>. {set e} # >} .=>. {set e}
# pipecase /1,1/1/#= $switchargs # pipecase /1,1/1/#= $switchargs
#} |@@ok/result> <e/0| [namespace qualifiers [lindex [info level -1] 0]] #} |@@ok/result> <e/0| [namespace qualifiers [lindex [info level -1] 0]]
@ -239,13 +242,13 @@ namespace eval punk::mix::base {
#puts stderr "-1:[info level -1]" #puts stderr "-1:[info level -1]"
set command_info [punk::mix::base::get_commands -extension $extension] set command_info [punk::mix::base::get_commands -extension $extension]
set subhelp1 [lindex $args 0] set subhelp1 [lindex $args 0]
if {[string length $subhelp1]} { if {[string length $subhelp1]} {
if {[regexp {[*?]} $subhelp1]} { if {[regexp {[*?]} $subhelp1]} {
set helpstr "" set helpstr ""
append helpstr "matched commands:\n" append helpstr "matched commands:\n"
dict for {source cmdlist} $command_info { dict for {source cmdlist} $command_info {
set matches [lsearch -all -inline -glob $cmdlist $subhelp1] set matches [lsearch -all -inline -glob $cmdlist $subhelp1]
if {[llength $matches]} { if {[llength $matches]} {
append helpstr \n " $source" append helpstr \n " $source"
foreach cmd $matches { foreach cmd $matches {
@ -268,7 +271,7 @@ namespace eval punk::mix::base {
} else { } else {
set a [interp alias {} ${ns}::$subhelp1] set a [interp alias {} ${ns}::$subhelp1]
if {[string length $a]} { if {[string length $a]} {
return "alias: $subhelp1 target: $a" return "alias: $subhelp1 target: $a"
} else { } else {
return "command: $subhelp1 (No info available)" return "command: $subhelp1 (No info available)"
} }
@ -295,7 +298,7 @@ namespace eval punk::mix::base {
return $helpstr return $helpstr
} }
#proc dostuff {args} { #proc dostuff {args} {
# extension@@opts/@?@-extension,args@@args= [_split_args $args] # extension@@opts/@?@-extension,args@@args= [_split_args $args]
# puts stdout "base doingstuff-with-args:'$args'-in-namespace:'[namespace current]'" # puts stdout "base doingstuff-with-args:'$args'-in-namespace:'[namespace current]'"
#} #}
namespace eval lib { namespace eval lib {
@ -335,7 +338,7 @@ namespace eval punk::mix::base {
if {![string length [set candidate [punk::repo::find_candidate $path]]]} { if {![string length [set candidate [punk::repo::find_candidate $path]]]} {
error "find_source_module_paths cannot determine a suitable project root at or above path '$path' - path supplied should be within a project" error "find_source_module_paths cannot determine a suitable project root at or above path '$path' - path supplied should be within a project"
} }
#we can return module paths even if the project isn't yet under revision control #we can return module paths even if the project isn't yet under revision control
set src_subs [glob -nocomplain -dir [file join $candidate src] -type d -tail *] set src_subs [glob -nocomplain -dir [file join $candidate src] -type d -tail *]
set antipatterns [list *.vfs vendor* lib _build doc embedded runtime bootsupport] set antipatterns [list *.vfs vendor* lib _build doc embedded runtime bootsupport]
set tm_folders [list] set tm_folders [list]
@ -343,8 +346,8 @@ namespace eval punk::mix::base {
set is_ok 1 set is_ok 1
foreach anti $antipatterns { foreach anti $antipatterns {
if {[string match $anti $sub]} { if {[string match $anti $sub]} {
set is_ok 0 set is_ok 0
break break
} }
} }
if {!$is_ok} { if {!$is_ok} {
@ -360,7 +363,7 @@ namespace eval punk::mix::base {
#set podfolders [glob -nocomplain -dir $testfolder -type d -tail #modpod-*] #set podfolders [glob -nocomplain -dir $testfolder -type d -tail #modpod-*]
if {[llength [glob -nocomplain -dir $testfolder -type f -tail *.tm]] || [llength [glob -nocomplain -dir $testfolder -type d -tail #modpod-*]]} { if {[llength [glob -nocomplain -dir $testfolder -type f -tail *.tm]] || [llength [glob -nocomplain -dir $testfolder -type d -tail #modpod-*]]} {
lappend tm_folders $testfolder lappend tm_folders $testfolder
} }
} }
return $tm_folders return $tm_folders
} }
@ -417,7 +420,7 @@ namespace eval punk::mix::base {
} }
#todo - move cksum stuff to punkcheck - more logical home #todo - move cksum stuff to punkcheck - more logical home
proc cksum_path_content {path args} { proc cksum_path_content {path args} {
dict set args -cksum_content 1 dict set args -cksum_content 1
@ -437,7 +440,7 @@ namespace eval punk::mix::base {
# - try builtin zlib crc instead? # - try builtin zlib crc instead?
#sha1 is performant - and this is not being used in a cryptographic or adversarial context - so performance and practical unlikelihood of accidental collisions should be the main consideration. #sha1 is performant - and this is not being used in a cryptographic or adversarial context - so performance and practical unlikelihood of accidental collisions should be the main consideration.
#adler32 is fastest for some larger files of a few MB but slower on small files (possibly due to Tcl-based file load?) #adler32 is fastest for some larger files of a few MB but slower on small files (possibly due to Tcl-based file load?)
#sha1 as at 2023 seems a reasonable default #sha1 as at 2023 seems a reasonable default
proc cksum_algorithms {} { proc cksum_algorithms {} {
variable sha3_implementation variable sha3_implementation
#sha2 is an alias for sha256 #sha2 is an alias for sha256
@ -445,11 +448,11 @@ namespace eval punk::mix::base {
set algs [list md5 sha1 sha2 sha256 cksum adler32] set algs [list md5 sha1 sha2 sha256 cksum adler32]
set sha3_algs [list sha3 sha3-224 sha3-256 sha3-384 sha3-512] set sha3_algs [list sha3 sha3-224 sha3-256 sha3-384 sha3-512]
if {[auto_execok sqlite3] ne ""} { if {[auto_execok sqlite3] ne ""} {
lappend algs {*}$sha3_algs lappend algs {*}$sha3_algs
set sha3_implementation sqlite3_sha3 set sha3_implementation sqlite3_sha3
} else { } else {
if {[auto_execok fossil] ne ""} { if {[auto_execok fossil] ne ""} {
lappend algs {*}$sha3_algs lappend algs {*}$sha3_algs
set sha3_implementation fossil_sha3 set sha3_implementation fossil_sha3
} }
} }
@ -465,16 +468,28 @@ namespace eval punk::mix::base {
#adler32 via file-slurp #adler32 via file-slurp
proc cksum_adler32_file {filename} { proc cksum_adler32_file {filename} {
package require zlib; #should be builtin anyway #2024 - zlib should be builtin - otherwise fallback to trf + zlibtcl?
set data [punk::mix::util::fcat -translation binary -encoding iso8859-1 $filename] set data [punk::mix::util::fcat -translation binary -encoding iso8859-1 $filename]
#set data [fileutil::cat -translation binary $filename] ;#no significant performance diff on windows - and doesn't handle win-illegal names #set data [fileutil::cat -translation binary $filename] ;#no significant performance diff on windows - and doesn't handle win-illegal names
zlib adler32 $data zlib adler32 $data
} }
#zlib crc vie file-slurp #zlib crc via file-slurp
proc cksum_crc_file {filename} { proc cksum_crc_file {filename} {
package require zlib
set data [punk::mix::util::fcat -translation binary -encoding iso8859-1 $filename] set data [punk::mix::util::fcat -translation binary -encoding iso8859-1 $filename]
zlib crc $data zlib crc32 $data
}
proc cksum_md5_data {data} {
if {[package vsatisfies [package present md5] 2-]} {
return [md5::md5 -hex $data]
} else {
return [md5::md5 $data]
}
}
#fallback md5 via file-slurp - shouldn't be needed if have md5 2-
proc cksum_md5_file {filename} {
set data [punk::mix::util::fcat -translation binary -encoding iso8859-1 $filename]
cksum_md5_data $data
} }
@ -491,7 +506,7 @@ namespace eval punk::mix::base {
} }
set base [file dirname $path] set base [file dirname $path]
set startdir [pwd] set startdir [pwd]
set defaults [cksum_default_opts] set defaults [cksum_default_opts]
set known_opts [dict keys $defaults] set known_opts [dict keys $defaults]
foreach {k v} $args { foreach {k v} $args {
@ -506,7 +521,7 @@ namespace eval punk::mix::base {
#if {![file exists $path]} { #if {![file exists $path]} {
# return [list cksum "" opts $opts] # return [list cksum "" opts $opts]
#} #}
if {[catch {file type $path} ftype]} { if {[catch {file type $path} ftype]} {
return [list cksum "<PATHNOTFOUND>" opts $opts] return [list cksum "<PATHNOTFOUND>" opts $opts]
} }
@ -605,7 +620,7 @@ namespace eval punk::mix::base {
if {$path eq $base} { if {$path eq $base} {
#attempting to cksum at root/volume level of a filesystem.. extra work #attempting to cksum at root/volume level of a filesystem.. extra work
#This needs fixing for general use.. not necessarily just for project repos #This needs fixing for general use.. not necessarily just for project repos
puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)" puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)"
return [list error unsupported_path opts $opts] return [list error unsupported_path opts $opts]
} }
@ -621,7 +636,11 @@ namespace eval punk::mix::base {
} }
md5 { md5 {
package require md5 package require md5
set cksum_command [list md5::md5 -hex -file] if {[package vsatisfies [package present md5] 2- ] } {
set cksum_command [list md5::md5 -hex -file]
} else {
set cksum_comand [list cksum_md5_file]
}
} }
cksum { cksum {
package require cksum ;#tcllib package require cksum ;#tcllib
@ -634,7 +653,7 @@ namespace eval punk::mix::base {
set cksum_command [list cksum_adler32_file] set cksum_command [list cksum_adler32_file]
} }
sha3 - sha3-256 { sha3 - sha3-256 {
#todo - replace with something that doesn't call another process #todo - replace with something that doesn't call another process - only if tcllibc not available!
#set cksum_command [list apply {{file} {lindex [exec fossil sha3sum -256 $file] 0}}] #set cksum_command [list apply {{file} {lindex [exec fossil sha3sum -256 $file] 0}}]
set cksum_command [list $sha3_implementation 256] set cksum_command [list $sha3_implementation 256]
} }
@ -652,19 +671,41 @@ namespace eval punk::mix::base {
set archivename $tmplocation/[punk::mix::util::tmpfile].tar set archivename $tmplocation/[punk::mix::util::tmpfile].tar
cd $base ;#cd is process-wide.. keep cd in effect for as small a scope as possible. (review for thread issues) cd $base ;#cd is process-wide.. keep cd in effect for as small a scope as possible. (review for thread issues)
#temp emission to stdout.. todo - repl telemetry channel #temp emission to stdout.. todo - repl telemetry channel
puts stdout "cksum_path: creating temporary tar archive for $path" puts stdout "cksum_path: creating temporary tar archive for $path"
puts stdout " at: $archivename .." puts -nonewline stdout " at: $archivename ..."
tar::create $archivename $target set tsstart [clock millis]
if {[set tarpath [auto_execok tar]] ne ""} {
#using an external binary is *significantly* faster than tar::create - but comes with some risks
#review - need to check behaviour/flag variances across platforms
#don't use -z flag. On at least some tar versions the zipped file will contain a timestamped subfolder of filename.tar - which ruins the checksum
#also - tar is generally faster without the compression (although this may vary depending on file size and disk speed?)
exec {*}$tarpath -cf $archivename $target ;#{*} needed in case spaces in tarpath
set tsend [clock millis]
set ms [expr {$tsend - $tsstart}]
puts stdout " tar -cf done ($ms ms)"
} else {
set tsstart [clock millis] ;#don't include auto_exec search time for tar::create
tar::create $archivename $target
set tsend [clock millis]
set ms [expr {$tsend - $tsstart}]
puts stdout " tar::create done ($ms ms)"
puts stdout " NOTE: install tar executable for potentially *much* faster directory checksum processing"
}
if {$ftype eq "file"} { if {$ftype eq "file"} {
set sizeinfo "(size [file size $target])" set sizeinfo "(size [punk::lib::format_number [file size $target]] bytes)"
} else { } else {
set sizeinfo "(file type $ftype - size unknown)" set sizeinfo "(file type $ftype - tarred size [punk::lib::format_number [file size $archivename]] bytes)"
} }
puts stdout "cksum_path: calculating cksum for $target $sizeinfo..." set tsstart [clock millis]
puts -nonewline stdout "cksum_path: calculating cksum using $opt_cksum_algorithm for $target $sizeinfo ... "
set cksum [{*}$cksum_command $archivename] set cksum [{*}$cksum_command $archivename]
#puts stdout "cksum_path: cleaning up.. " set tsend [clock millis]
set ms [expr {$tsend - $tsstart}]
puts stdout " cksum done ($ms ms)"
puts stdout " cksum: $cksum"
file delete -force $archivename file delete -force $archivename
cd $startdir cd $startdir
@ -677,7 +718,7 @@ namespace eval punk::mix::base {
set cksum [{*}$cksum_command $path] set cksum [{*}$cksum_command $path]
} }
} else { } else {
error "cksum_path unsupported $opts for path type [file type $path]" error "cksum_path unsupported $opts for path type [file type $path]"
} }
} }
set result [dict create] set result [dict create]
@ -692,7 +733,7 @@ namespace eval punk::mix::base {
#base can be empty string in which case paths must be absolute #base can be empty string in which case paths must be absolute
#expect dict_path_cksum to be a dict keyed on relpath where each value is a dictionary with keys cksum and opts #expect dict_path_cksum to be a dict keyed on relpath where each value is a dictionary with keys cksum and opts
# ie subdict for <path> can be created from output of cksum_path <path> (for already known values not requiring filling) # ie subdict for <path> can be created from output of cksum_path <path> (for already known values not requiring filling)
# or cksum "" opts [cksum_default_opts] or cksum "" opts {} (for cksum to be filled using supplied cksum opts if any) # or cksum "" opts [cksum_default_opts] or cksum "" opts {} (for cksum to be filled using supplied cksum opts if any)
proc fill_relativecksums_from_base_and_relativepathdict {base {dict_path_cksum {}}} { proc fill_relativecksums_from_base_and_relativepathdict {base {dict_path_cksum {}}} {
if {$base eq ""} { if {$base eq ""} {
set error_paths [list] set error_paths [list]
@ -726,6 +767,8 @@ namespace eval punk::mix::base {
dict for {path pathinfo} $dict_path_cksum { dict for {path pathinfo} $dict_path_cksum {
puts "fill_relativecksums_from_base_and_relativepathdict-->$path REVIEW"
#review to see if we process same path repeatedly, so could avoid repeated 'file exists $fullpath' below by caching a glob
if {![dict exists $pathinfo cksum]} { if {![dict exists $pathinfo cksum]} {
dict set pathinfo cksum "" dict set pathinfo cksum ""
} else { } else {
@ -734,7 +777,7 @@ namespace eval punk::mix::base {
} }
} }
if {$base ne ""} { if {$base ne ""} {
set fullpath [file join $base $path] set fullpath [file join $base $path]
} else { } else {
set fullpath $path set fullpath $path
} }
@ -779,7 +822,7 @@ namespace eval punk::mix::base {
#Here we will raise an error if cksum exists and is not empty or a tag - whereas the multiple path version will honour valid-looking prefilled cksum values (ie will pass them through) #Here we will raise an error if cksum exists and is not empty or a tag - whereas the multiple path version will honour valid-looking prefilled cksum values (ie will pass them through)
#base is the presumed location to store the checksum file. The caller should retain (normalize if relative) #base is the presumed location to store the checksum file. The caller should retain (normalize if relative)
proc get_relativecksum_from_base {base specifiedpath args} { proc get_relativecksum_from_base {base specifiedpath args} {
if {$base ne ""} { if {$base ne ""} {
#targetpath ideally should be within same project tree as base if base supplied - but not necessarily below it #targetpath ideally should be within same project tree as base if base supplied - but not necessarily below it
#we don't necessarily want to restrict this to use in punk projects though - so we'll allow anything with a common prefix #we don't necessarily want to restrict this to use in punk projects though - so we'll allow anything with a common prefix
if {[file pathtype $specifiedpath] eq "relative"} { if {[file pathtype $specifiedpath] eq "relative"} {
@ -805,12 +848,12 @@ namespace eval punk::mix::base {
#absolute base with no shared prefix doesn't make sense - we could ignore it - but better to error-out and require the caller specify an empty base #absolute base with no shared prefix doesn't make sense - we could ignore it - but better to error-out and require the caller specify an empty base
error "get_relativecksum_from_base error: base '$base' and specifiedpath '$specifiedpath' don't share a common root. Use empty-string for base if independent absolute path is required" error "get_relativecksum_from_base error: base '$base' and specifiedpath '$specifiedpath' don't share a common root. Use empty-string for base if independent absolute path is required"
} }
set targetpath $specifiedpath set targetpath $specifiedpath
set storedpath [punk::path::relative $base $specifiedpath] set storedpath [punk::path::relative $base $specifiedpath]
} }
} else { } else {
if {[file type $specifiedpath] eq "relative"} { if {[file pathtype $specifiedpath] eq "relative"} {
#if specifiedpath is relative - and we don't have a base, we now need to convert relative to cwd to an absolute path for storage #if specifiedpath is relative - and we don't have a base, we now need to convert relative to cwd to an absolute path for storage
set targetpath [file normalize $specifiedpath] set targetpath [file normalize $specifiedpath]
set storedpath $targetpath set storedpath $targetpath
@ -822,13 +865,13 @@ namespace eval punk::mix::base {
# #
#NOTE: specifiedpath can be a relative path (to cwd) when base is empty #NOTE: specifiedpath can be a relative path (to cwd) when base is empty
#OR - a relative path when base itself is relative e.g base: somewhere targetpath somewhere/etc #OR - a relative path when base itself is relative e.g base: somewhere targetpath somewhere/etc
#possibly also: base: somewhere targetpath: ../elsewhere/etc #possibly also: base: somewhere targetpath: ../elsewhere/etc
# #
#todo - write tests #todo - write tests
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' " error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' "
} }
if {[dict exists $args cksum]} { if {[dict exists $args cksum]} {
@ -840,7 +883,7 @@ namespace eval punk::mix::base {
set ckopts [cksum_filter_opts {*}$args] set ckopts [cksum_filter_opts {*}$args]
set ckinfo [cksum_path $targetpath {*}$ckopts] set ckinfo [cksum_path $targetpath {*}$ckopts]
set keyvals $args ;# REVIEW set keyvals $args ;# REVIEW
dict set keyvals cksum [dict get $ckinfo cksum] dict set keyvals cksum [dict get $ckinfo cksum]
#dict set keyvals cksum_all_opts [dict get $ckinfo opts] #dict set keyvals cksum_all_opts [dict get $ckinfo opts]
@ -850,7 +893,7 @@ namespace eval punk::mix::base {
} }
#set relpath [punk::repo::path_strip_alreadynormalized_prefixdepth $fullpath $base] ;#empty base ok noop #set relpath [punk::repo::path_strip_alreadynormalized_prefixdepth $fullpath $base] ;#empty base ok noop
#storedpath is relative if possible #storedpath is relative if possible
return [dict create $storedpath $keyvals] return [dict create $storedpath $keyvals]
} }
@ -869,7 +912,8 @@ namespace eval punk::mix::base {
dict set dict_cksums [file join $buildrelpath $vname.exe] [list cksum ""] dict set dict_cksums [file join $buildrelpath $vname.exe] [list cksum ""]
} }
#buildruntime.exe obsolete.. #buildruntime.exe obsolete..
puts stderr "warning obsolete? get_all_vfs_build_cksums 'buildruntime.exe'???"
set fullpath_buildruntime $buildfolder/buildruntime.exe set fullpath_buildruntime $buildfolder/buildruntime.exe
set ckinfo_buildruntime [cksum_path $fullpath_buildruntime] set ckinfo_buildruntime [cksum_path $fullpath_buildruntime]
@ -903,7 +947,7 @@ namespace eval punk::mix::base {
} }
proc get_all_build_cksums_stored {path} { proc get_all_build_cksums_stored {path} {
set buildfolder [get_build_workdir $path] set buildfolder [get_build_workdir $path]
set vfscontainer [file dirname $buildfolder] set vfscontainer [file dirname $buildfolder]
set vfslist [glob -nocomplain -dir $vfscontainer -type d -tail *.vfs] set vfslist [glob -nocomplain -dir $vfscontainer -type d -tail *.vfs]
set dict_cksums [dict create] set dict_cksums [dict create]
@ -922,7 +966,7 @@ namespace eval punk::mix::base {
} }
set vfscontainer [file dirname $vfsfolder] set vfscontainer [file dirname $vfsfolder]
set buildfolder $vfscontainer/_build set buildfolder $vfscontainer/_build
set dict_vfs [get_vfs_build_cksums $vfsfolder] set dict_vfs [get_vfs_build_cksums $vfsfolder]
set data "" set data ""
dict for {path cksum} $dict_vfs { dict for {path cksum} $dict_vfs {
append data "$path $cksum" \n append data "$path $cksum" \n

376
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm vendored

@ -7,7 +7,7 @@
# (C) 2023 # (C) 2023
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::mix::cli 0.3.1 # Application punk::mix::cli 0.3.1
# Meta platform tcl # Meta platform tcl
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
@ -19,7 +19,7 @@
##e.g package require frobz ##e.g package require frobz
package require punk::repo package require punk::repo
package require punk::ansi package require punk::ansi
package require punkcheck ;#checksum and/or timestamp records package require punkcheck ;#checksum and/or timestamp records
@ -31,47 +31,58 @@ namespace eval punk::mix::cli {
namespace eval temp_import { namespace eval temp_import {
} }
namespace ensemble create namespace ensemble create
variable initialised 0
package require punk::overlay #lazy _init - called by punk::mix::base::_cli when ensemble used
catch { proc _init {args} {
punk::overlay::import_commandset module . ::punk::mix::commandset::module variable initialised
} if {$initialised} {
punk::overlay::import_commandset debug . ::punk::mix::commandset::debug return
punk::overlay::import_commandset repo . ::punk::mix::commandset::repo }
punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib puts stderr "punk::mix::cli::init $args"
package require punk::overlay
catch { namespace eval ::punk::mix::cli {
package require punk::mix::commandset::project catch {
punk::overlay::import_commandset project . ::punk::mix::commandset::project punk::overlay::import_commandset module . ::punk::mix::commandset::module
punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection }
} punk::overlay::import_commandset debug . ::punk::mix::commandset::debug
if {[catch { punk::overlay::import_commandset repo . ::punk::mix::commandset::repo
package require punk::mix::commandset::layout punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib
punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection catch {
} errM]} { package require punk::mix::commandset::project
puts stderr "error loading punk::mix::commandset::layout" punk::overlay::import_commandset project . ::punk::mix::commandset::project
puts stderr $errM punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
} }
if {[catch { if {[catch {
package require punk::mix::commandset::buildsuite package require punk::mix::commandset::layout
punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection
} errM]} { } errM]} {
puts stderr "error loading punk::mix::commandset::buildsuite" puts stderr "error loading punk::mix::commandset::layout"
puts stderr $errM puts stderr $errM
} }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap if {[catch {
if {[catch { package require punk::mix::commandset::buildsuite
package require punk::mix::commandset::doc punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection } errM]} {
} errM]} { puts stderr "error loading punk::mix::commandset::buildsuite"
puts stderr "error loading punk::mix::commandset::doc" puts stderr $errM
puts stderr $errM }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap
if {[catch {
package require punk::mix::commandset::doc
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection
} errM]} {
puts stderr "error loading punk::mix::commandset::doc"
puts stderr $errM
}
}
set initialised 1
} }
proc help {args} { proc help {args} {
#set basehelp [punk::mix::base::help -extension [namespace current] {*}$args] #set basehelp [punk::mix::base::help -extension [namespace current] {*}$args]
set basehelp [punk::mix::base help {*}$args] set basehelp [punk::mix::base help {*}$args]
@ -80,12 +91,12 @@ namespace eval punk::mix::cli {
} }
proc stat {{workingdir ""} args} { proc stat {{workingdir ""} args} {
dict set args -v 0 dict set args -v 0
punk::mix::cli::lib::get_status $workingdir {*}$args punk::mix::cli::lib::get_status $workingdir {*}$args
} }
proc status {{workingdir ""} args} { proc status {{workingdir ""} args} {
dict set args -v 1 dict set args -v 1
punk::mix::cli::lib::get_status $workingdir {*}$args punk::mix::cli::lib::get_status $workingdir {*}$args
} }
@ -117,13 +128,13 @@ namespace eval punk::mix::cli {
set project_base [punk::repo::find_candidate] set project_base [punk::repo::find_candidate]
set sourcefolder $project_base/src set sourcefolder $project_base/src
puts stderr "WARNING - project not under git or fossil control" puts stderr "WARNING - project not under git or fossil control"
puts stderr "Using base folder $project_base" puts stderr "Using base folder $project_base"
} else { } else {
set sourcefolder $startdir set sourcefolder $startdir
} }
} }
#review - why can't we be anywhere in the project? #review - why can't we be anywhere in the project?
#also - if no make.tcl - can we use the running shell's make.tcl ? (after prompting user?) #also - if no make.tcl - can we use the running shell's make.tcl ? (after prompting user?)
if {([file tail $sourcefolder] ne "src") || (![file exists $sourcefolder/make.tcl])} { if {([file tail $sourcefolder] ne "src") || (![file exists $sourcefolder/make.tcl])} {
puts stderr "dev make must be run from src folder containing make.tcl - unable to proceed (cwd: [pwd])" puts stderr "dev make must be run from src folder containing make.tcl - unable to proceed (cwd: [pwd])"
@ -146,7 +157,7 @@ namespace eval punk::mix::cli {
if {![string length $project_base]} { if {![string length $project_base]} {
puts stderr "WARNING no git or fossil repository detected." puts stderr "WARNING no git or fossil repository detected."
puts stderr "Using base folder $startdir" puts stderr "Using base folder $startdir"
set project_base $startdir set project_base $startdir
} }
@ -166,7 +177,8 @@ namespace eval punk::mix::cli {
} }
} }
} }
cd $sourcefolder #cd $sourcefolder
#use run so that stdout visible as it goes #use run so that stdout visible as it goes
if {![catch {run --timeout=55000 -debug [info nameofexecutable] $sourcefolder/make.tcl {*}$args} exitinfo]} { if {![catch {run --timeout=55000 -debug [info nameofexecutable] $sourcefolder/make.tcl {*}$args} exitinfo]} {
#todo - notify if exit because of timeout! #todo - notify if exit because of timeout!
@ -174,11 +186,11 @@ namespace eval punk::mix::cli {
set exitcode [dict get $exitinfo exitcode] set exitcode [dict get $exitinfo exitcode]
} else { } else {
puts stderr "Error unable to determine exitcode. err: $exitinfo" puts stderr "Error unable to determine exitcode. err: $exitinfo"
cd $startdir #cd $startdir
return false return false
} }
cd $startdir #cd $startdir
if {$exitcode != 0} { if {$exitcode != 0} {
puts stderr "FAILED with exitcode $exitcode" puts stderr "FAILED with exitcode $exitcode"
return false return false
@ -186,7 +198,7 @@ namespace eval punk::mix::cli {
puts stdout "OK make finished " puts stdout "OK make finished "
return true return true
} }
} }
proc Kettle {args} { proc Kettle {args} {
tailcall lib::kettle_call lib {*}$args tailcall lib::kettle_call lib {*}$args
@ -210,11 +222,12 @@ namespace eval punk::mix::cli {
proc validate_modulename {modulename args} { proc validate_modulename {modulename args} {
set opts [list\ set opts [list\
-errorprefix validate_modulename\ -errorprefix validate_modulename\
-strict 0\
] ]
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"}
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-errorprefix { -errorprefix - -strict {
dict set opts $k $v dict set opts $k $v
} }
default { default {
@ -223,8 +236,14 @@ namespace eval punk::mix::cli {
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_errorprefix [dict get $opts -errorprefix] set opt_errorprefix [dict get $opts -errorprefix]
set opt_strict [dict get $opts -strict]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
if {$opt_strict} {
if {[regexp {[A-Z]} $modulename]} {
error "$opt_errorprefix '$modulename' contains uppercase which is not recommended as per tip 590, and option -strict is set to 1"
}
}
validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix
set testname [string map {:: {}} $modulename] set testname [string map {:: {}} $modulename]
@ -239,6 +258,56 @@ namespace eval punk::mix::cli {
} }
return $modulename return $modulename
} }
proc confirm_modulename {modulename} {
set finalised 0
set aborted 0
while {!$finalised && !$aborted} {
#first validate with -strict 0 to confirm acceptable while ignoring case issues.
#uppercase is generally valid but not recommended - so has separate prompting.
if {[catch {validate_modulename $modulename -strict 0} errM]} {
set msg "Chosen name didn't pass validation\n"
append msg "reason: $errM\n"
append msg "Please retype the modulename. You will be given a further prompt to confirm or abort."
set modulename [util::askuser $msg]
} elseif {[regexp {[A-Z]} $modulename]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$modulename' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $modulename} {
#ok - user insists
set finalised 1
} else {
#user supplied a different uppercase name - don't set finalised so we bug them again to type it two times the same way to proceed
puts stdout "A different uppercase name was supplied - reconfirmation required."
}
set modulename $answer
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $modulename]} {
set finalised 1
} else {
#.. but it doesn't match original - require rerun
}
set modulename $answer
}
} else {
set answer [util::askuser "Proceed with the module name '$modulename'? Y to continue N to abort"]
if {[string tolower $answer] eq "y"} {
set finalised 1
} else {
set aborted 1
}
}
}
if {$aborted} {
return [dict create status error reason errmsg]
} else {
return [dict create status ok modulename $modulename]
}
}
proc validate_projectname {projectname args} { proc validate_projectname {projectname args} {
set defaults [list\ set defaults [list\
@ -263,7 +332,7 @@ namespace eval punk::mix::cli {
if {[string first "::" $projectname] >= 0} { if {[string first "::" $projectname] >= 0} {
error "$opt_errorprefix '$projectname' cannot contain namespace separator '::'" error "$opt_errorprefix '$projectname' cannot contain namespace separator '::'"
} }
return $projectname return $projectname
} }
proc validate_name_not_empty_or_spaced {name args} { proc validate_name_not_empty_or_spaced {name args} {
set opts [list\ set opts [list\
@ -296,10 +365,10 @@ namespace eval punk::mix::cli {
#ignore trailing .tm .TM if present #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 #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. #Up to caller to validate.
proc split_modulename_version {modulename} { proc split_modulename_version {fullmodulename} {
set lastpart [namespace tail $modulename] 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 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] -] set fileparts [split [file rootname $lastpart] -]
} else { } else {
set fileparts [split $lastpart -] set fileparts [split $lastpart -]
@ -312,14 +381,20 @@ namespace eval punk::mix::cli {
set namesegment [join $fileparts -] set namesegment [join $fileparts -]
set versionsegment "" 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} { proc get_status {{workingdir ""} args} {
set result "" set result ""
if {$workingdir ne ""} { if {$workingdir ne ""} {
if {[file pathtype $workingdir] ne "absolute"} { if {[file pathtype $workingdir] ne "absolute"} {
set workingdir [file normalize $workingdir] set workingdir [file normalize $workingdir]
} }
set active_dir $workingdir set active_dir $workingdir
} else { } else {
@ -328,21 +403,21 @@ namespace eval punk::mix::cli {
set defaults [dict create\ set defaults [dict create\
-v 1\ -v 1\
] ]
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- ---
set opt_v [dict get $opts -v] set opt_v [dict get $opts -v]
# -- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- ---
set repopaths [punk::repo::find_repos [pwd]] set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos] set repos [dict get $repopaths repos]
if {![llength $repos]} { if {![llength $repos]} {
append result [dict get $repopaths warnings] append result [punk::ansi::a+ bold yellow][dict get $repopaths warnings][punk::ansi::a]
} else { } else {
append result [dict get $repopaths warnings] append result [punk::ansi::a+ bold yellow][dict get $repopaths warnings][punk::ansi::a]
lassign [lindex $repos 0] repopath repotypes lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} { if {"fossil" in $repotypes} {
#review - multiple process launches to fossil a bit slow on windows.. #review - multiple process launches to fossil a bit slow on windows..
#could we query global db in one go instead? #could we query global db in one go instead?
# #
set fossil_prog [auto_execok fossil] set fossil_prog [auto_execok fossil]
@ -369,14 +444,14 @@ namespace eval punk::mix::cli {
if {"project" in $repotypes} { if {"project" in $repotypes} {
#punk project #punk project
if {![catch {package require textblock; package require patternpunk}]} { if {![catch {package require textblock; package require patternpunk}]} {
set result [textblock::join -- [>punk . logo] " " $result] set result [textblock::join -- [>punk . logo] " " $result]
append result \n append result \n
} }
} }
set timeline [exec fossil timeline -n 5 -t ci] set timeline [exec fossil timeline -n 5 -t ci]
set timeline [string map {\r\n \n} $timeline] set timeline [string map {\r\n \n} $timeline]
append result $timeline append result $timeline
if {$opt_v} { if {$opt_v} {
set repostate [punk::repo::workingdir_state $repopath -repopaths $repopaths -repotypes fossil] set repostate [punk::repo::workingdir_state $repopath -repopaths $repopaths -repotypes fossil]
append result \n [punk::repo::workingdir_state_summary $repostate] append result \n [punk::repo::workingdir_state_summary $repostate]
@ -441,7 +516,7 @@ namespace eval punk::mix::cli {
puts stderr "Use: >build_modules_from_source_to_base /x/src/modules2 /x/modules2 -subdirlist {skunkworks lib}" puts stderr "Use: >build_modules_from_source_to_base /x/src/modules2 /x/modules2 -subdirlist {skunkworks lib}"
exit 2 exit 2
} }
set srcdirname [file tail $srcdir] set srcdirname [file tail $srcdir]
set build [file dirname $srcdir]/_build/$srcdirname ;#relative to *original* srcdir - not current_source_dir set build [file dirname $srcdir]/_build/$srcdirname ;#relative to *original* srcdir - not current_source_dir
if {[llength $subdirlist] == 0} { if {[llength $subdirlist] == 0} {
@ -503,7 +578,7 @@ namespace eval punk::mix::cli {
} }
set fileparts [split [file rootname $modpath] -] set fileparts [split [file rootname $modpath] -]
#set tmfile_versionsegment [lindex $fileparts end] #set tmfile_versionsegment [lindex $fileparts end]
lassign [split_modulename_version $modpath] basename tmfile_versionsegment lassign [split_modulename_version $modpath] basename tmfile_versionsegment
if {$tmfile_versionsegment eq ""} { if {$tmfile_versionsegment eq ""} {
#split_modulename_version version part will be empty if not valid tcl version #split_modulename_version version part will be empty if not valid tcl version
#last segment doesn't look even slightly versiony - fail. #last segment doesn't look even slightly versiony - fail.
@ -547,6 +622,8 @@ namespace eval punk::mix::cli {
-glob *\ -glob *\
-max_depth 100\ -max_depth 100\
] ]
set had_error 0
# -max_depth -1 for no limit # -max_depth -1 for no limit
set build_installername pods_in_$current_source_dir set build_installername pods_in_$current_source_dir
set build_installer [punkcheck::installtrack new $build_installername $buildfolder/.punkcheck] set build_installer [punkcheck::installtrack new $build_installername $buildfolder/.punkcheck]
@ -557,8 +634,8 @@ namespace eval punk::mix::cli {
set modulefile $buildfolder/$basename-$module_build_version.tm set modulefile $buildfolder/$basename-$module_build_version.tm
$build_event targetset_init INSTALL $podtree_copy $build_event targetset_init INSTALL $podtree_copy
$build_event targetset_addsource $current_source_dir/$modpath $build_event targetset_addsource $current_source_dir/$modpath
if {$tmfile_versionsegment eq $magicversion} { if {$tmfile_versionsegment eq $magicversion} {
$build_event targetset_addsource $versionfile $build_event targetset_addsource $versionfile
} }
@ -590,7 +667,7 @@ namespace eval punk::mix::cli {
if {[file exists $tmfile]} { if {[file exists $tmfile]} {
set newname $buildfolder/#modpod-$basename-$module_build_version/$basename-$module_build_version.tm set newname $buildfolder/#modpod-$basename-$module_build_version/$basename-$module_build_version.tm
file rename $tmfile $newname file rename $tmfile $newname
set tmfile $newname set tmfile $newname
} }
set fd [open $tmfile r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd set fd [open $tmfile r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd
set data [string map [list $magicversion $module_build_version] $data] set data [string map [list $magicversion $module_build_version] $data]
@ -600,7 +677,6 @@ namespace eval punk::mix::cli {
close $fdout close $fdout
} }
#delete and regenerate zip and modpod stubbed zip #delete and regenerate zip and modpod stubbed zip
set had_error 0
set notes [list] set notes [list]
if {[catch { if {[catch {
file delete $buildfolder/$basename-$module_build_version.zip file delete $buildfolder/$basename-$module_build_version.zip
@ -618,20 +694,37 @@ namespace eval punk::mix::cli {
package require punk::zip package require punk::zip
set zipfile $buildfolder/$basename-$module_build_version.zip ;#ordinary zip file (deflate) set zipfile $buildfolder/$basename-$module_build_version.zip ;#ordinary zip file (deflate)
if 0 { #zipfs mkzip does exactly what we need anyway in this case
#unfortunately it's not available in all Tclsh versions we might be running..
if {[llength [info commands zipfs]]} {
#zipfs mkzip (2025) doesn't add entries for folders.
#(Therefore no timestamps)
#zip reading utils generally intuit their existence and display them - but often an editor can't add comments to them
set wd [pwd]
cd $buildfolder
puts "zipfs mkzip $zipfile #modpod-$basename-$module_build_version"
zipfs mkzip $zipfile #modpod-$basename-$module_build_version
cd $wd
} else {
#use -base $buildfolder so that -directory is included in the archive - the modpod stub relies on this - and extraction would be potentially messy otherwise #use -base $buildfolder so that -directory is included in the archive - the modpod stub relies on this - and extraction would be potentially messy otherwise
#put in an archive-level comment to aid in debugging
#punk
punk::zip::mkzip -base $buildfolder -directory $buildfolder/#modpod-$basename-$module_build_version -- $zipfile * punk::zip::mkzip -base $buildfolder -directory $buildfolder/#modpod-$basename-$module_build_version -- $zipfile *
#punk::zip::mkzip stores permissions - (unix style) which confuses zipfs when reading - it misidentifies dirs as files #punk::zip::mkzip stores permissions - (unix style) - which zipfs mkzip doesn't
#Directory ident in zipfs relies on folders ending with trailing slash - if missing, it misidentifies dirs as files.
#(ie it can't use permissions/attributes alone to determine directory vs file)
#TODO - review punk::zip::mkzip and/or external zip to provide a fallback?
#JMN25
#set had_error 1
#lappend notes "zipfs_unavailable"
#puts stderr "WARNING: zipfs unavailable can't build $modulefile"
}
if {!$had_error && [file exists $zipfile]} {
package require modpod
modpod::lib::make_zip_modpod $zipfile $modulefile
} }
#zipfs mkzip does exactly what we need anyway in this case
set wd [pwd]
cd $buildfolder
puts "zipfs mkzip $zipfile #modpod-$basename-$module_build_version"
zipfs mkzip $zipfile #modpod-$basename-$module_build_version
cd $wd
package require modpod
modpod::lib::make_zip_modpod $zipfile $modulefile
if {$had_error} { if {$had_error} {
@ -646,41 +739,50 @@ namespace eval punk::mix::cli {
} }
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "P"
set did_skip 1 set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record] #set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$build_event targetset_end SKIPPED $build_event targetset_end SKIPPED
} }
$build_event destroy $build_event destroy
$build_installer destroy $build_installer destroy
$event targetset_init INSTALL $target_module_dir/$basename-$module_build_version.tm #JMN - review
$event targetset_addsource $modulefile if {!$had_error} {
if {\ $event targetset_init INSTALL $target_module_dir/$basename-$module_build_version.tm
[llength [dict get [$event targetset_source_changes] changed]]\ $event targetset_addsource $modulefile
|| [llength [$event get_targets_exist]] < [llength [$event get_targets]]\ if {\
} { [llength [dict get [$event targetset_source_changes] changed]]\
|| [llength [$event get_targets_exist]] < [llength [$event get_targets]]\
$event targetset_started } {
# -- --- --- --- --- ---
if {$did_skip} {set did_skip 0; puts -nonewline stdout \n} $event targetset_started
lappend module_list $modulefile # -- --- --- --- --- ---
file copy -force $modulefile $target_module_dir if {$did_skip} {set did_skip 0; puts -nonewline stdout \n}
puts stderr "Copied zip modpod module $modulefile to $target_module_dir" lappend module_list $modulefile
# -- --- --- --- --- --- if {[catch {
$event targetset_end OK -note "zip modpod" file copy -force $modulefile $target_module_dir
} else { } errMsg]} {
puts -nonewline stderr "." puts stderr "FAILED to copy zip modpod module $modulefile to $target_module_dir"
set did_skip 1 $event targetset_end FAILED -note "could not copy $modulefile"
if {$is_interesting} { } else {
puts stderr "$modulefile [$event targetset_source_changes]" puts stderr "Copied zip modpod module $modulefile to $target_module_dir"
# -- --- --- --- --- ---
$event targetset_end OK -note "zip modpod"
}
} else {
puts -nonewline stderr "p"
set did_skip 1
if {$is_interesting} {
puts stderr "$modulefile [$event targetset_source_changes]"
}
$event targetset_end SKIPPED
} }
$event targetset_end SKIPPED
} }
} }
tarjar { tarjar {
#basename may still contain #tarjar- #basename may still contain #tarjar-
#to be obsoleted - update modpod to (optionally) use vfs::tar #to be obsoleted - update modpod to (optionally) use vfs::tar
} }
file { file {
set m $modpath set m $modpath
@ -706,12 +808,12 @@ namespace eval punk::mix::cli {
if {[file exists $current_source_dir/#tarjar-$basename-$magicversion]} { if {[file exists $current_source_dir/#tarjar-$basename-$magicversion]} {
#rebuild the .tm from the #tarjar #rebuild the .tm from the #tarjar
if {[file exists $current_source_dir/#tarjar-$basename-$magicversion/DESCRIPTION.txt]} { if {[file exists $current_source_dir/#tarjar-$basename-$magicversion/DESCRIPTION.txt]} {
} else { } else {
} }
#REVIEW - should be in same structure/depth as $target_module_dir in _build? #REVIEW - should be in same structure/depth as $target_module_dir in _build?
@ -722,22 +824,22 @@ namespace eval punk::mix::cli {
set tmfile $buildfolder/$basename-$module_build_version.tm set tmfile $buildfolder/$basename-$module_build_version.tm
file delete -force $buildfolder/#tarjar-$basename-$module_build_version file delete -force $buildfolder/#tarjar-$basename-$module_build_version
file delete -force $tmfile file delete -force $tmfile
file copy -force $current_source_dir/#tarjar-$basename-$magicversion $buildfolder/#tarjar-$basename-$module_build_version file copy -force $current_source_dir/#tarjar-$basename-$magicversion $buildfolder/#tarjar-$basename-$module_build_version
# #
#bsdtar doesn't seem to work.. or I haven't worked out the right options? #bsdtar doesn't seem to work.. or I haven't worked out the right options?
#exec tar -cvf $buildfolder/$basename-$module_build_version.tm $buildfolder/#tarjar-$basename-$module_build_version #exec tar -cvf $buildfolder/$basename-$module_build_version.tm $buildfolder/#tarjar-$basename-$module_build_version
package require tar package require tar
tar::create $tmfile $buildfolder/#tarjar-$basename-$module_build_version tar::create $tmfile $buildfolder/#tarjar-$basename-$module_build_version
if {![file exists $tmfile]} { if {![file exists $tmfile]} {
puts stdout "ERROR: failed to build tarjar file $tmfile" puts stdout "ERROR: failed to build tarjar file $tmfile"
exit 4 exit 4
} }
#copy the file? #copy the file?
#set target $target_module_dir/$basename-$module_build_version.tm #set target $target_module_dir/$basename-$module_build_version.tm
#file copy -force $tmfile $target #file copy -force $tmfile $target
lappend module_list $tmfile lappend module_list $tmfile
} else { } else {
#assume that either the .tm is not a tarjar - or the tarjar dir is capped (trailing #) and the .tm has been manually tarred. #assume that either the .tm is not a tarjar - or the tarjar dir is capped (trailing #) and the .tm has been manually tarred.
@ -749,7 +851,7 @@ namespace eval punk::mix::cli {
# #
#set target_relpath [punkcheck::lib::path_relative $basedir $target_module_dir/$basename-$module_build_version.tm] #set target_relpath [punkcheck::lib::path_relative $basedir $target_module_dir/$basename-$module_build_version.tm]
#set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid] #set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid]
$event targetset_init INSTALL $target_module_dir/$basename-$module_build_version.tm $event targetset_init INSTALL $target_module_dir/$basename-$module_build_version.tm
$event targetset_addsource $versionfile $event targetset_addsource $versionfile
$event targetset_addsource $current_source_dir/$m $event targetset_addsource $current_source_dir/$m
@ -791,7 +893,7 @@ namespace eval punk::mix::cli {
if {$is_interesting} { if {$is_interesting} {
puts stdout "skipping module $current_source_dir/$m - no change in sources detected" puts stdout "skipping module $current_source_dir/$m - no change in sources detected"
} }
puts -nonewline stderr "." puts -nonewline stderr "m"
set did_skip 1 set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record] #set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$event targetset_end SKIPPED $event targetset_end SKIPPED
@ -800,7 +902,7 @@ namespace eval punk::mix::cli {
#------------------------------ #------------------------------
} }
continue continue
} }
##------------------------------ ##------------------------------
@ -815,7 +917,7 @@ namespace eval punk::mix::cli {
#set changed_unchanged [punkcheck::recordlist::file_install_record_source_changes [lindex [dict get $file_record body] end]] #set changed_unchanged [punkcheck::recordlist::file_install_record_source_changes [lindex [dict get $file_record body] end]]
#set changed_list [dict get $changed_unchanged changed] #set changed_list [dict get $changed_unchanged changed]
#---------- #----------
$event targetset_init INSTALL $target_module_dir/$m $event targetset_init INSTALL $target_module_dir/$m
$event targetset_addsource $current_source_dir/$m $event targetset_addsource $current_source_dir/$m
if {\ if {\
[llength [dict get [$event targetset_source_changes] changed]]\ [llength [dict get [$event targetset_source_changes] changed]]\
@ -833,7 +935,7 @@ namespace eval punk::mix::cli {
#set file_record [punkcheck::installfile_finished_install $basedir $file_record] #set file_record [punkcheck::installfile_finished_install $basedir $file_record]
$event targetset_end OK -note "already versioned module" $event targetset_end OK -note "already versioned module"
} else { } else {
puts -nonewline stderr "." puts -nonewline stderr "f"
set did_skip 1 set did_skip 1
if {$is_interesting} { if {$is_interesting} {
puts stderr "$current_source_dir/$m [$event targetset_source_changes]" puts stderr "$current_source_dir/$m [$event targetset_source_changes]"
@ -849,7 +951,8 @@ namespace eval punk::mix::cli {
if {$CALLDEPTH >= $max_depth} { if {$CALLDEPTH >= $max_depth} {
set subdirs [list] set subdirs [list]
} else { } else {
set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *] set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *]
set targets_existing [glob -nocomplain -dir $target_module_dir -type d -tail {*}$subdirs]
} }
#puts stderr "subdirs: $subdirs" #puts stderr "subdirs: $subdirs"
foreach d $subdirs { foreach d $subdirs {
@ -863,7 +966,10 @@ namespace eval punk::mix::cli {
if {$skipdir} { if {$skipdir} {
continue continue
} }
if {![file exists $target_module_dir/$d]} { #if {![file exists $target_module_dir/$d]} {
# file mkdir $target_module_dir/$d
#}
if {$d ni $targets_existing} {
file mkdir $target_module_dir/$d file mkdir $target_module_dir/$d
} }
lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\ lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\
@ -879,7 +985,7 @@ namespace eval punk::mix::cli {
} }
if {$CALLDEPTH == 0} { if {$CALLDEPTH == 0} {
$event destroy $event destroy
$installer destroy $installer destroy
} }
return $module_list return $module_list
} }
@ -915,7 +1021,7 @@ namespace eval punk::mix::cli {
} }
dict set kettle_reset_args $p $arglist dict set kettle_reset_args $p $arglist
} }
} }
} }
#call kettle_reinit to ensure recipes point to current project #call kettle_reinit to ensure recipes point to current project
@ -993,14 +1099,14 @@ namespace eval punk::mix::cli {
kettle_reinit kettle_reinit
} }
} }
set first [lindex $args 0] set first [lindex $args 0]
if {[string match @* $first]} { if {[string match @* $first]} {
error "deck kettle doesn't support special operations - try calling tclsh kettle directly" error "deck kettle doesn't support special operations - try calling tclsh kettle directly"
} }
if {$first eq "-f"} { if {$first eq "-f"} {
set args [lassign $args __ path] set args [lassign $args __ path]
} else { } else {
set path $startdir/build.tcl set path $startdir/build.tcl
} }
set opts [list] set opts [list]
@ -1021,9 +1127,9 @@ namespace eval punk::mix::cli {
} }
} }
#hardcoded kettle option names (::kettle option names) - retrieved using kettle::option names #hardcoded kettle option names (::kettle option names) - retrieved using kettle::option names
#This is done so we don't have to load kettle lib for shell call (both loading as module and running shell are annoyingly SLOW) #This is done so we don't have to load kettle lib for shell call (both loading as module and running shell are annoyingly SLOW)
#REVIEW - needs to be updated to keep in sync with kettle. #REVIEW - needs to be updated to keep in sync with kettle.
set knownopts [list\ set knownopts [list\
--exec-prefix --bin-dir --lib-dir --prefix --man-dir --html-dir --markdown-dir --include-dir \ --exec-prefix --bin-dir --lib-dir --prefix --man-dir --html-dir --markdown-dir --include-dir \
--ignore-glob --dry --verbose --machine --color --state --config --with-shell --log \ --ignore-glob --dry --verbose --machine --color --state --config --with-shell --log \
@ -1100,7 +1206,7 @@ namespace eval punk::mix::cli {
package require punk::mix::base package require punk::mix::base
package require punk::overlay package require punk::overlay
if {[catch { if {[catch {
punk::overlay::custom_from_base [namespace current] ::punk::mix::base punk::overlay::custom_from_base [namespace current] ::punk::mix::base
} errM]} { } errM]} {
puts stderr "punk::mix::cli load error: Failed to overlay punk::mix::base $errM" puts stderr "punk::mix::cli load error: Failed to overlay punk::mix::base $errM"
error "punk::mix::cli error: $errM" error "punk::mix::cli error: $errM"
@ -1111,9 +1217,9 @@ namespace eval punk::mix::cli {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::cli [namespace eval punk::mix::cli { package provide punk::mix::cli [namespace eval punk::mix::cli {
variable version variable version
set version 0.3.1 set version 0.3.1
}] }]
return return

1128
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.tm vendored

File diff suppressed because it is too large Load Diff

2
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/buildsuite-0.1.0.tm vendored

@ -49,7 +49,7 @@ namespace eval punk::mix::commandset::buildsuite {
set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok. set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok.
set s [lindex $path_parts end-1] set s [lindex $path_parts end-1]
set p [lindex $path_parts end] set p [lindex $path_parts end]
#This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl #This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl
#so we can't just use tail as dict key. We could assume last record is always total - but #so we can't just use tail as dict key. We could assume last record is always total - but
if {![string match -nocase $s $suite]} { if {![string match -nocase $s $suite]} {

8
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/debug-0.1.0.tm vendored

@ -26,7 +26,7 @@ namespace eval punk::mix::commandset::debug {
namespace export get paths namespace export get paths
namespace path ::punk::mix::cli namespace path ::punk::mix::cli
#Except for 'get' - all debug commands should emit to stdout #Except for 'get' - all debug commands should emit to stdout
proc paths {} { proc paths {} {
set out "" set out ""
puts stdout "find_repos output:" puts stdout "find_repos output:"
@ -40,7 +40,7 @@ namespace eval punk::mix::commandset::debug {
set template_base_dict [punk::mix::base::lib::get_template_basefolders] set template_base_dict [punk::mix::base::lib::get_template_basefolders]
puts stdout "get_template_basefolders output:" puts stdout "get_template_basefolders output:"
pdict template_base_dict */* pdict template_base_dict */*
return return
} }
#call other debug command - but capture stdout as return value #call other debug command - but capture stdout as return value
@ -84,9 +84,9 @@ namespace eval punk::mix::commandset::debug {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug { package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

42
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm vendored

@ -22,7 +22,7 @@ package require punk::path ;# for treefilenames, relative
package require punk::repo package require punk::repo
package require punk::docgen ;#inline doctools - generate doctools .man files at src/docgen prior to using kettle to producing .html .md etc package require punk::docgen ;#inline doctools - generate doctools .man files at src/docgen prior to using kettle to producing .html .md etc
package require punk::mix::cli ;#punk::mix::cli::lib used for kettle_call package require punk::mix::cli ;#punk::mix::cli::lib used for kettle_call
#package require punk::mix::util ;#for path_relative #package require punkcheck ;#for path_relative
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -48,6 +48,7 @@ namespace eval punk::mix::commandset::doc {
set oldfiles [punk::path::treefilenames -dir $projectdir/src/doc _module_*.man] set oldfiles [punk::path::treefilenames -dir $projectdir/src/doc _module_*.man]
foreach maybedoomed $oldfiles { foreach maybedoomed $oldfiles {
set fd [open $maybedoomed r] set fd [open $maybedoomed r]
chan conf $fd -translation binary
set data [read $fd] set data [read $fd]
close $fd close $fd
if {[string match "*--- punk::docgen overwrites *" $data]} { if {[string match "*--- punk::docgen overwrites *" $data]} {
@ -165,7 +166,18 @@ namespace eval punk::mix::commandset::doc {
cd $original_wd cd $original_wd
} }
proc validate {} { proc validate {args} {
set argd [punk::args::get_dict {
@id -id ::punk::mix::commandset::doc::validate
-- -type none -optional 1 -help "end of options marker --"
-individual -type boolean -default 1
@values -min 0 -max -1
patterns -default {*.man} -type any -multiple 1
} $args]
set opt_individual [tcl::dict::get $argd opts -individual]
set patterns [tcl::dict::get $argd values patterns]
#todo - run and validate punk::docgen output #todo - run and validate punk::docgen output
set projectdir [punk::repo::find_project] set projectdir [punk::repo::find_project]
if {$projectdir eq ""} { if {$projectdir eq ""} {
@ -180,7 +192,23 @@ namespace eval punk::mix::commandset::doc {
set docroot $projectdir/src/doc set docroot $projectdir/src/doc
cd $docroot cd $docroot
dtplite validate $docroot if {!$opt_individual && "*.man" in $patterns} {
if {[catch {
dtplite validate $docroot
} errM]} {
puts stderr "commandset::doc::validate failed for projectdir '$projectdir'"
puts stderr "docroot '$docroot'"
puts stderr "dtplite error was: $errM"
}
} else {
foreach p $patterns {
set treefiles [punk::path::treefilenames $p]
foreach path $treefiles {
puts stdout "dtplite validate $path"
dtplite validate $path
}
}
}
#punk::mix::cli::lib::kettle_call lib validate-doc #punk::mix::cli::lib::kettle_call lib validate-doc
@ -225,6 +253,7 @@ namespace eval punk::mix::commandset::doc {
append docgen_header_comments {[comment {--- punk::docgen DO NOT EDIT DOCS HERE UNLESS YOU REMOVE THESE COMMENT LINES ---}]} \n append docgen_header_comments {[comment {--- punk::docgen DO NOT EDIT DOCS HERE UNLESS YOU REMOVE THESE COMMENT LINES ---}]} \n
append docgen_header_comments {[comment {--- punk::docgen overwrites this file ---}]} \n append docgen_header_comments {[comment {--- punk::docgen overwrites this file ---}]} \n
foreach fullpath $matched_paths { foreach fullpath $matched_paths {
puts stdout "do_docgen processing: $fullpath"
set doctools [punk::docgen::get_doctools_comments $fullpath] set doctools [punk::docgen::get_doctools_comments $fullpath]
if {$doctools ne ""} { if {$doctools ne ""} {
set fname [file tail $fullpath] set fname [file tail $fullpath]
@ -245,7 +274,12 @@ namespace eval punk::mix::commandset::doc {
#this case only came up because docgen used to path munge to long filenames - but left because we know there is a limit and renaming fixes it - even if it's ugly - but still allows doc generation. #this case only came up because docgen used to path munge to long filenames - but left because we know there is a limit and renaming fixes it - even if it's ugly - but still allows doc generation.
#review - if we're checking fname - should also test length of whole path and determine limits for tar #review - if we're checking fname - should also test length of whole path and determine limits for tar
package require md5 package require md5
set target_docname [md5::md5 -hex [encoding convertto utf-8 $fullpath]]_overlongfilename.man if {[package vsatisfies [package present md5] 2- ] } {
set md5opt "-hex"
} else {
set md5opt ""
}
set target_docname [md5::md5 {*}$md5opt [encoding convertto utf-8 $fullpath]]_overlongfilename.man
puts stderr "WARNING - overlong file name - renaming $fullpath" puts stderr "WARNING - overlong file name - renaming $fullpath"
puts stderr " to [file dirname $fullpath]/$target_docname" puts stderr " to [file dirname $fullpath]/$target_docname"
} }

118
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm vendored

@ -22,7 +22,8 @@ package require punk::args
#sort of a circular dependency when commandset loaded by punk::mix::cli - that's ok, but this could theoretically be loaded by another cli and with another base #sort of a circular dependency when commandset loaded by punk::mix::cli - that's ok, but this could theoretically be loaded by another cli and with another base
package require punk::mix package require punk::mix
package require punk::mix::base package require punk::mix::base
package require punk::lib
package require textblock
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -30,19 +31,48 @@ namespace eval punk::mix::commandset::layout {
namespace export * 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 #per layout functions
proc files {{layout ""}} { punk::args::define {
set argd [punk::args::get_dict { @dynamic
*values -min 1 -max 1 @id -id ::punk::mix::commandset::layout::files
layout -type string -minlen 1 -datetime -default "%Y-%m-%dT%H:%M:%S" -help\
} [list $layout]] "Datetime format for mtime. Use empty string for no datetime output"
@values -min 1 -max 1
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] set allfiles [lib::layout_all_files $layout]
return [join $allfiles \n] 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} { proc templatefiles {layout} {
set templatefiles [lib::layout_scan_for_template_files $layout] set templatefiles_and_tags [lib::layout_scan_for_template_files $layout]
return [join $templatefiles \n] set flatlist [punk::lib::lmapflat v $templatefiles_and_tags {lrange $v 0 end}]
#return [join $templatefiles \n]
textblock::list_as_table -header {"File with tags found" "Tags"} -columns 2 $flatlist
} }
proc templatefiles.relative {layout} { proc templatefiles.relative {layout} {
@ -56,12 +86,14 @@ namespace eval punk::mix::commandset::layout {
set stripprefix [file normalize $layoutfolder] set stripprefix [file normalize $layoutfolder]
set templatefiles [lib::layout_scan_for_template_files $layout] set templatefiles_and_tags [lib::layout_scan_for_template_files $layout]
set tails [list] set flatlist [list]
foreach templatefullpath $templatefiles { foreach entry $templatefiles_and_tags {
lappend tails [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] lassign $entry templatefullpath tags
lappend flatlist [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] $tags
} }
return [join $tails \n] #return [join $tails \n]
textblock::list_as_table -header {"File with tags found" "Tags"} -columns 2 $flatlist
} }
#layout collection functions - to be imported with punk::overlay::import_commandset separately #layout collection functions - to be imported with punk::overlay::import_commandset separately
@ -83,7 +115,8 @@ namespace eval punk::mix::commandset::layout {
proc _default {args} { proc _default {args} {
punk::args::get_dict [subst { punk::args::get_dict [subst {
*proc -name ::punk::mix::commandset::layout::collection::_default @id -id ::punk::mix::commandset::layout::collection::_default
@cmd -name ::punk::mix::commandset::layout::collection::_default
-startdir -type string -startdir -type string
-not -type string -multiple 1 -not -type string -multiple 1
globsearches -default * -multiple 1 globsearches -default * -multiple 1
@ -110,28 +143,16 @@ namespace eval punk::mix::commandset::layout {
} }
set title(path) "Path" set title(path) "Path"
set widest(path) [tcl::mathfunc::max {*}[lmap v [concat [list $title(path)] $paths] {punk::strlen $v}]]
set col(path) [string repeat " " $widest(path)]
set title(pathtype) "[a+ green]Path Type[a]" set title(pathtype) "[a+ green]Path Type[a]"
set widest(pathtype) [tcl::mathfunc::max {*}[lmap v [concat [list $title(pathtype)] $pathtypes] {punk::strlen $v}]]
set col(pathtype) [string repeat " " $widest(pathtype)]
set title(name) "Layout Name" set title(name) "Layout Name"
set widest(name) [tcl::mathfunc::max {*}[lmap v [concat [list $title(name)] $names] {punk::strlen $v}]]
set col(name) [string repeat " " $widest(name)]
set vsep " | "
set vsep_w [string length $vsep] ;#unicode?
set tablewidth [expr {$widest(name) + $vsep_w + $widest(pathtype) + $vsep_w + $widest(path)}]
set table ""
append table [string repeat - $tablewidth] \n
append table "[textblock::join -- [overtype::left $col(name) $title(name)] $vsep [overtype::left $col(pathtype) $title(pathtype)] $vsep [overtype::left $col(path) $title(path)]]" \n
append table [string repeat - $tablewidth] \n
set data [list]
foreach n $names pt $pathtypes p $paths { foreach n $names pt $pathtypes p $paths {
append table "[textblock::join -- [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n lappend data $n $pt $p
} }
set table [textblock::list_as_table -columns 3 -header [list $title(name) $title(pathtype) $title(path)] $data]
return $table return $table
} }
@ -156,35 +177,22 @@ namespace eval punk::mix::commandset::layout {
lappend pathtypes [dict get $tinfo sourceinfo pathtype] lappend pathtypes [dict get $tinfo sourceinfo pathtype]
} }
set title(path) "Path"
set widest(path) [tcl::mathfunc::max {*}[lmap v [concat [list $title(path)] $paths] {punk::strlen $v}]]
set col(path) [string repeat " " $widest(path)]
set title(pathtype) "[a+ green]Path Type[a]"
set widest(pathtype) [tcl::mathfunc::max {*}[lmap v [concat [list $title(pathtype)] $pathtypes] {punk::strlen $v}]]
set col(pathtype) [string repeat " " $widest(pathtype)]
set title(name) "Layout Name" set title(name) "Layout Name"
set widest(name) [tcl::mathfunc::max {*}[lmap v [concat [list $title(name)] $names] {punk::strlen $v}]] set title(pathtype) "[a+ green]Path Type[a]"
set col(name) [string repeat " " $widest(name)] set title(path) "Path"
set vsep " | "
set vsep_w [string length $vsep] ;#unicode?
set tablewidth [expr {$widest(name) + $vsep_w + $widest(pathtype) + $vsep_w + $widest(path)}]
set table ""
append table [string repeat - $tablewidth] \n
append table "[textblock::join -- [overtype::left $col(name) $title(name)] $vsep [overtype::left $col(pathtype) $title(pathtype)] $vsep [overtype::left $col(path) $title(path)]]" \n
append table [string repeat - $tablewidth] \n
set data [list]
foreach n $names pt $pathtypes p $paths { foreach n $names pt $pathtypes p $paths {
append table "[textblock::join -- [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n #append table "[textblock::join -- [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n
lappend data $n $pt $p
} }
set table [textblock::list_as_table -columns 3 -header [list $title(name) $title(pathtype) $title(path)] $data]
return $table return $table
} }
proc as_dict {args} { 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} { proc references_as_dict {args} {
package require punk::cap package require punk::cap
@ -243,7 +251,7 @@ namespace eval punk::mix::commandset::layout {
#todo - get standard tags from somewhere #todo - get standard tags from somewhere
set tagnames [list project] set tagnames [list project]
foreach tn $tagnames { foreach tn $tagnames {
lappend tags [string cat % $tn %] lappend tags [string cat % $tn %] ;#make sure actual tag literal doesn't appear in this source file
} }
} }
set file_list [list] set file_list [list]
@ -252,11 +260,15 @@ namespace eval punk::mix::commandset::layout {
fconfigure $fd -translation binary fconfigure $fd -translation binary
set data [read $fd] set data [read $fd]
close $fd close $fd
foreach tag $tags { set found_tags [list]
foreach tag $tags tn $tagnames {
if {[string match "*$tag*" $data]} { if {[string match "*$tag*" $data]} {
lappend file_list $path lappend found_tags $tn
} }
} }
if {[llength $found_tags]} {
lappend file_list [list $path $found_tags]
}
} }
return $file_list return $file_list

43
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm vendored

@ -1,5 +1,5 @@
# -*- tcl -*- # -*- 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. # 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. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -26,19 +26,25 @@ package require punk::lib
namespace eval punk::mix::commandset::loadedlib { namespace eval punk::mix::commandset::loadedlib {
namespace export * namespace export *
#search automatically wrapped in * * - can contain inner * ? globs #search automatically wrapped in * * - can contain inner * ? globs
proc search {args} { punk::args::define {
set argspecs { @id -id ::punk::mix::commandset::loadedlib::search
*proc -name "punk::mix::commandset::loadedlib search" -help "search all Tcl libraries available to your local interpreter" @cmd -name "punk::mix::commandset::loadedlib search" -help "search all Tcl libraries available to your local interpreter"
-return -type string -default table -choices {table tableobject list lines} -return -type string -default table -choices {table tableobject list lines}
-present -type integer -default 2 -choices {0 1 2} -choicelabels {absent present both} -help "(unimplemented) Display only those that are 0:absent 1:present 2:both" -present -type integer -default 2 -choices {0 1 2} -choicelabels {absent present both} -help\
-highlight -type boolean -default 1 -help "Highlight which version is present with ansi underline and colour" "(unimplemented) Display only those that are 0:absent 1:present 2:either"
-refresh -default 0 -type boolean -help "Re-scan the tm and library folders" -highlight -type boolean -default 1 -help\
searchstrings -default * -multiple 1 -help "Names to search for, may contain glob chars (* ?) e.g *lib* "Highlight which version is present with ansi underline and colour"
-refresh -default 0 -type boolean -help "Re-scan the tm and library folders"
searchstrings -default * -multiple 1 -help\
"Names to search for, may contain glob chars (* ?) e.g *lib*
If no glob chars are explicitly specified, the searchstring will be wrapped with star globs. If no glob chars are explicitly specified, the searchstring will be wrapped with star globs.
eg name -> *name* eg name -> *name*
To search for an exact name prefix it with =
e.g =name -> name
" "
} }
set argd [punk::args::get_dict $argspecs $args] proc search {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::loadedlib::search $args]
set searchstrings [dict get $argd values searchstrings] set searchstrings [dict get $argd values searchstrings]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set opt_return [dict get $opts -return] set opt_return [dict get $opts -return]
@ -53,7 +59,7 @@ namespace eval punk::mix::commandset::loadedlib {
set packages [package names] set packages [package names]
set matches [list] set matches [list]
foreach search $searchstrings { foreach search $searchstrings {
if {[regexp {[?*]} $search]} { if {[regexp {[?*\[]} $search]} {
#caller has specified specific glob pattern - use it #caller has specified specific glob pattern - use it
#todo - respect supplied case only if uppers present? require another flag? #todo - respect supplied case only if uppers present? require another flag?
lappend matches {*}[lsearch -all -inline -nocase $packages $search] lappend matches {*}[lsearch -all -inline -nocase $packages $search]
@ -298,11 +304,12 @@ namespace eval punk::mix::commandset::loadedlib {
} }
set versions [package versions [lindex $libfound 0]] set versions [package versions [lindex $libfound 0]]
if {$has_natsort} { set versions [lsort -command {package vcompare} $versions]
set versions [natsort::sort $versions] #if {$has_natsort} {
} else { # set versions [natsort::sort $versions]
set versions [lsort $versions] #} else {
} # set versions [lsort $versions]
#}
if {![llength $versions]} { if {![llength $versions]} {
error "No version numbers found for library/module $libfound - sorry, you will need to copy it across manually" error "No version numbers found for library/module $libfound - sorry, you will need to copy it across manually"
} }

126
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm vendored

@ -26,8 +26,10 @@ namespace eval punk::mix::commandset::module {
namespace export * namespace export *
proc paths {} { proc paths {} {
set roots [punk::repo::find_repos ""] #set roots [punk::repo::find_repos ""]
set project [lindex [dict get $roots project] 0] #set project [lindex [dict get $roots project] 0]
set project [punk::repo::find_project ""]
if {$project ne ""} { if {$project ne ""} {
set is_project 1 set is_project 1
set searchbase $project set searchbase $project
@ -120,16 +122,20 @@ namespace eval punk::mix::commandset::module {
return $table return $table
} }
#return all module templates with repeated ones suffixed with .2 .3 etc
#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"
-not -default "" -multiple 1
@values
globsearches -default * -multiple 1
}
proc templates_dict {args} { proc templates_dict {args} {
set argspec { set argd [punk::args::get_by_id ::punk::mix::commandset::module::templates_dict $args]
*proc -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]
package require punk::cap package require punk::cap
if {[punk::cap::capability_has_handler punk.templates]} { if {[punk::cap::capability_has_handler punk.templates]} {
set template_folder_dict [punk::cap::call_handler punk.templates get_itemdict_moduletemplates {*}$args] set template_folder_dict [punk::cap::call_handler punk.templates get_itemdict_moduletemplates {*}$args]
@ -137,23 +143,40 @@ namespace eval punk::mix::commandset::module {
put stderr "get_template_basefolders WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations" put stderr "get_template_basefolders WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
} }
} }
set moduletypes [punk::mix::cli::lib::module_types]
punk::args::define [subst {
@id -id ::punk::mix::commandset::module::new
@cmd -name "punk::mix::commandset::module::new" -help\
"Create a new module file in the appropriate folder within src/modules.
If the name given in the module argument is namespaced,
the necessary subfolder(s) will be used or created."
-project -optional 1
-version -default "0.1.0" -help\
"version to use if not specified as part of the module argument.
If a version is specified in the module argument as well as in -version
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 false (default) an error will be raised if there is a conflict."
-quiet -default 0 -type boolean -help\
"Suppress information messages on stdout"
@values -min 1 -max 1
module -type string -help\
"Name of module, possibly including a namespace and/or version number
e.g mynamespace::mymodule-1.0"
}]
proc new {args} { proc new {args} {
set year [clock format [clock seconds] -format %Y] set year [clock format [clock seconds] -format %Y]
set moduletypes [punk::mix::cli::lib::module_types]
# use \uFFFD because unicode replacement char should consistently render as 1 wide # use \uFFFD because unicode replacement char should consistently render as 1 wide
set argspecs [subst { set argd [punk::args::get_by_id ::punk::mix::commandset::module::new $args]
-project -default \uFFFD lassign [dict values $argd] leaders opts values received
-version -default \uFFFD
-license -default <unspecified>
-template -default punk.module
-type -default \uFFFD -choices {$moduletypes}
-force -default 0 -type boolean
-quiet -default 0 -type boolean
*values -min 1 -max 1
module -type string
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values
set module [dict get $values module] set module [dict get $values module]
#set opts [dict merge $defaults $args] #set opts [dict merge $defaults $args]
@ -168,13 +191,9 @@ namespace eval punk::mix::commandset::module {
# we need this value before looking at the named argument # we need this value before looking at the named argument
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_version_supplied [dict get $opts -version] set opt_version_supplied [dict get $opts -version]
if {$opt_version_supplied eq "\uFFFD"} { set opt_version $opt_version_supplied
set opt_version "0.1.0" if {![util::is_valid_tm_version $opt_version]} {
} else { error "deck module.new error - supplied -version $opt_version doesn't appear to be a valid Tcl module version"
set opt_version $opt_version_supplied
if {![util::is_valid_tm_version $opt_version]} {
error "deck module.new error - supplied -version $opt_version doesn't appear to be a valid Tcl module version"
}
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#named argument #named argument
@ -194,7 +213,7 @@ namespace eval punk::mix::commandset::module {
} else { } else {
set vmsg "from -version option: $opt_version_supplied" set vmsg "from -version option: $opt_version_supplied"
} }
if {$opt_version_supplied ne "\uFFFD"} { if {"-version" in $received} {
if {$vcompare_is_mversion_bigger != 0} { if {$vcompare_is_mversion_bigger != 0} {
#is bigger or smaller #is bigger or smaller
puts stderr "module.new WARNING: version supplied in module argument as well as -version option. Using the higher version number $vmsg" puts stderr "module.new WARNING: version supplied in module argument as well as -version option. Using the higher version number $vmsg"
@ -204,10 +223,33 @@ namespace eval punk::mix::commandset::module {
set modulename $module set modulename $module
} }
punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new"
if {[regexp {[A-Z]} $module]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$module' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $module} {
#ok - user insists
} else {
}
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $module]} {
set module $answer
} else {
#.. but it doesn't match original - require rerun
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#options #options
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_project [dict get $opts -project]
set testdir [pwd] set testdir [pwd]
if {![string length [set projectdir [punk::repo::find_project $testdir]]]} { if {![string length [set projectdir [punk::repo::find_project $testdir]]]} {
if {![string length [set projectdir [punk::repo::find_candidate $testdir]]]} { if {![string length [set projectdir [punk::repo::find_candidate $testdir]]]} {
@ -215,9 +257,10 @@ namespace eval punk::mix::commandset::module {
error "module.new unable to create module in projectdir:$projectdir - directory doesn't appear to meet basic standards $msg" error "module.new unable to create module in projectdir:$projectdir - directory doesn't appear to meet basic standards $msg"
} }
} }
if {$opt_project == "\uFFFF"} { if {![dict exists $received -project]} {
set projectname [file tail $projectdir] set projectname [file tail $projectdir]
} else { } else {
set opt_project [dict get $opts -project]
set projectname $opt_project set projectname $opt_project
if {$projectname ne [file tail $projectdir]} { if {$projectname ne [file tail $projectdir]} {
error "module.new -project '$opt_project' doesn't match detected projectname '$projectname' at path: $projectdir" error "module.new -project '$opt_project' doesn't match detected projectname '$projectname' at path: $projectdir"
@ -225,6 +268,7 @@ namespace eval punk::mix::commandset::module {
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_license [dict get $opts -license] set opt_license [dict get $opts -license]
set opt_authors [dict get $opts -author] ;#-multiple true
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_template [dict get $opts -template] set opt_template [dict get $opts -template]
if {[regexp {.*[?*].*} $opt_template]} { if {[regexp {.*[?*].*} $opt_template]} {
@ -285,12 +329,6 @@ namespace eval punk::mix::commandset::module {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_type [dict get $opts -type] set opt_type [dict get $opts -type]
if {$opt_type eq "\uFFFD"} {
set opt_type [lindex [punk::mix::cli::lib::module_types] 0] ;#default to plain
}
if {$opt_type ni [punk::mix::cli::lib::module_types]} {
error "module.new - error - unknown -type '$opt_type' known-types: [punk::mix::cli::lib::module_types]"
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_quiet [dict get $opts -quiet] set opt_quiet [dict get $opts -quiet]
set opt_force [dict get $opts -force] set opt_force [dict get $opts -force]
@ -372,7 +410,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 #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 #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] set strmap [list]
foreach {tag val} $tagnames { foreach {tag val} $tagnames {
lappend strmap %$tag% $val lappend strmap %$tag% $val
@ -383,7 +421,7 @@ namespace eval punk::mix::commandset::module {
set podfile $modulefolder/#modpod-$moduletail-$infile_version/$moduletail-$infile_version.tm set podfile $modulefolder/#modpod-$moduletail-$infile_version/$moduletail-$infile_version.tm
set has_tm [file exists $tmfile] set has_tm [file exists $tmfile]
set has_pod [file exists $podfile] set has_pod [file exists $podfile]
if {$has_tm && $has_pos} { if {$has_tm && $has_pod} {
#invalid configuration - bomb out #invalid configuration - bomb out
error "module.new error: Invalid target configuration found. module folder has both a .tm file $tmfile and a modpod file $podfile. Please delete one of them before trying again." error "module.new error: Invalid target configuration found. module folder has both a .tm file $tmfile and a modpod file $podfile. Please delete one of them before trying again."
} }
@ -424,7 +462,7 @@ namespace eval punk::mix::commandset::module {
} }
set buildversionfile [file join $modulefolder ${moduletail}-buildversion.txt] set buildversionfile [file join $modulefolder ${moduletail}-buildversion.txt]
set existing_build_version "" set existing_build_version ""
if {[file exists $buildversionfile]} { if {!$opt_force && [file exists $buildversionfile]} {
set buildversiondata [punk::mix::util::fcat $buildversionfile] set buildversiondata [punk::mix::util::fcat $buildversionfile]
set lines [split $buildversiondata \n] set lines [split $buildversiondata \n]
set existing_build_version [string trim [lindex $lines 0]] set existing_build_version [string trim [lindex $lines 0]]

253
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm vendored

@ -20,7 +20,7 @@
#[manpage_begin punkshell_module_punk::mix::commandset::project 0 0.1.0] #[manpage_begin punkshell_module_punk::mix::commandset::project 0 0.1.0]
#[copyright "2023"] #[copyright "2023"]
#[titledesc {dec commandset - project}] [comment {-- Name section and table of contents description --}] #[titledesc {dec commandset - project}] [comment {-- Name section and table of contents description --}]
#[moddesc {deck CLI commandset - project}] [comment {-- Description at end of page heading --}] #[moddesc {deck CLI commandset - project}] [comment {-- Description at end of page heading --}]
#[require punk::mix::commandset::project] #[require punk::mix::commandset::project]
#[description] #[description]
@ -29,25 +29,25 @@
#*** !doctools #*** !doctools
#[section Overview] #[section Overview]
#[para] overview of punk::mix::commandset::project #[para] overview of punk::mix::commandset::project
#[para]Import into an ensemble namespace similarly to the way it is done with punk::mix::cli e.g #[para]Import into an ensemble namespace similarly to the way it is done with punk::mix::cli e.g
#[example { #[example {
# namespace eval myproject::cli { # namespace eval myproject::cli {
# namespace export * # namespace export *
# namespace ensemble create # namespace ensemble create
# package require punk::overlay # package require punk::overlay
# #
# package require punk::mix::commandset::project # package require punk::mix::commandset::project
# punk::overlay::import_commandset project . ::punk::mix::commandset::project # punk::overlay::import_commandset project . ::punk::mix::commandset::project
# punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection # punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
# } # }
#}] #}]
#[para] Where the . in the above example is the prefix/command separator #[para] Where the . in the above example is the prefix/command separator
#[para]The prefix ('project' in the above example) can be any string desired to disambiguate commands imported from other commandsets. #[para]The prefix ('project' in the above example) can be any string desired to disambiguate commands imported from other commandsets.
#[para]The above results in the availability of the ensemble command: ::myproject::cli project.new, which is implemented in ::punk::mix::commandset::project::new #[para]The above results in the availability of the ensemble command: ::myproject::cli project.new, which is implemented in ::punk::mix::commandset::project::new
#[para]Similarly, procs under ::punk::mix::commandset::project::collection will be available as subcommands of the ensemble as <ensemblecommand> projects.<procname> #[para]Similarly, procs under ::punk::mix::commandset::project::collection will be available as subcommands of the ensemble as <ensemblecommand> projects.<procname>
#[para] #[para]
#[subsection Concepts] #[subsection Concepts]
#[para] see punk::overlay #[para] see punk::overlay
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -56,7 +56,7 @@
#*** !doctools #*** !doctools
#[subsection dependencies] #[subsection dependencies]
#[para] packages used by punk::mix::commandset::project #[para] packages used by punk::mix::commandset::project
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
@ -88,7 +88,7 @@ namespace eval punk::mix::commandset::project {
namespace export * namespace export *
#*** !doctools #*** !doctools
#[subsection {Namespace punk::mix::commandset::project}] #[subsection {Namespace punk::mix::commandset::project}]
#[para] core commandset functions for punk::mix::commandset::project #[para] core commandset functions for punk::mix::commandset::project
#[list_begin definitions] #[list_begin definitions]
proc _default {} { proc _default {} {
@ -109,12 +109,31 @@ 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} { proc new {newprojectpath_or_name args} {
#*** !doctools #*** !doctools
# [call [fun new] [arg newprojectpath_or_name] [opt args]] # [call [fun new] [arg newprojectpath_or_name] [opt args]]
#new project structure - may be dedicated to one module, or contain many. #new project structure - may be dedicated to one module, or contain many.
#create minimal folder structure only by specifying in args: -modules {} #create minimal folder structure only by specifying in args: -modules {}
if {[file pathtype $newprojectpath_or_name] eq "absolute"} { if {[file pathtype $newprojectpath_or_name] eq "absolute"} {
set projectfullpath [file normalize $newprojectpath_or_name] set projectfullpath [file normalize $newprojectpath_or_name]
@ -157,21 +176,21 @@ namespace eval punk::mix::commandset::project {
set opt_force [dict get $opts -force] set opt_force [dict get $opts -force]
set opt_confirm [string tolower [dict get $opts -confirm]] set opt_confirm [string tolower [dict get $opts -confirm]]
# -- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_layout [dict get $opts -layout]
set opt_update [dict get $opts -update]
# -- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_modules [dict get $opts -modules] set opt_modules [dict get $opts -modules]
if {[llength $opt_modules] == 1 && [lindex $opt_modules 0] eq "\uFFFF"} { if {[llength $opt_modules] == 1 && [lindex $opt_modules 0] eq "\uFFFF"} {
#if not specified - add a single module matching project name #if not specified - add a single module matching project name
if {$opt_force || $opt_update} { if {$opt_force || $opt_update} {
#generally undesirable to add default project module during an update. #generally undesirable to add default project module during an update.
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list $projectname] set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_layout [dict get $opts -layout]
set opt_update [dict get $opts -update]
# -- --- --- --- --- --- --- --- --- --- --- --- ---
#todo - install support binaries on a per-project basis in a way that doesn't impact machine (e.g not added to path) - cache in user config dir if possible, supply mechanism to clear cache #todo - install support binaries on a per-project basis in a way that doesn't impact machine (e.g not added to path) - cache in user config dir if possible, supply mechanism to clear cache
@ -188,12 +207,12 @@ namespace eval punk::mix::commandset::project {
} }
#we don't assume 'unknown' is configured to run shell commands #we don't assume 'unknown' is configured to run shell commands
if {[string length [package provide shellrun]]} { if {[string length [package provide shellrun]]} {
set exitinfo [run {*}$scoop_prog install fossil] set exitinfo [run {*}$scoop_prog install fossil]
#scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use. #scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use.
puts stdout "scoop install fossil ran with result: $exitinfo" puts stdout "scoop install fossil ran with result: $exitinfo"
} else { } else {
puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )" puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )"
set result [exec {*}$scoop_prog install fossil] set result [exec {*}$scoop_prog install fossil]
puts stdout $result puts stdout $result
} }
catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"') catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"')
@ -285,7 +304,7 @@ namespace eval punk::mix::commandset::project {
} }
} }
set project_dir_exists [file exists $projectdir] set project_dir_exists [file exists $projectdir]
if {$project_dir_exists && !($opt_force || $opt_update)} { if {$project_dir_exists && !($opt_force || $opt_update)} {
puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template" puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template"
@ -300,10 +319,20 @@ namespace eval punk::mix::commandset::project {
} }
} }
} elseif {$project_dir_exists && $opt_update} { } 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 "" set fossil_repo_file ""
set is_fossil_root 0 set is_fossil_root 0
if {$project_dir_exists && [punk::repo::is_fossil_root $projectdir]} { if {$project_dir_exists && [punk::repo::is_fossil_root $projectdir]} {
set is_fossil_root 1 set is_fossil_root 1
@ -327,7 +356,7 @@ namespace eval punk::mix::commandset::project {
return return
} }
#review #review
set fossil_repo_file $repodb_folder/$projectname.fossil set fossil_repo_file $repodb_folder/$projectname.fossil
} }
if {$fossil_repo_file eq ""} { if {$fossil_repo_file eq ""} {
@ -349,7 +378,7 @@ namespace eval punk::mix::commandset::project {
file mkdir $projectdir file mkdir $projectdir
puts stdout ">>> about to call punkcheck::install $layout_path $projectdir" puts stdout ">>> about to call punkcheck::install $layout_path $projectdir"
set resultdict [dict create] set resultdict [dict create]
set antipaths [list\ set antipaths [list\
src/doc/*\ src/doc/*\
@ -365,33 +394,45 @@ 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 #default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized
if {$opt_force} { if {$opt_force} {
puts stdout "copying layout files - with force applied - overwrite all-targets" 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 { } else {
puts stdout "copying layout files - (if source file changed)" 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] puts stdout [punkcheck::summarize_install_resultdict $resultdict]
puts stdout "copying layout src/doc files (if target missing)" if {[file exists $layout_path/src/doc]} {
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS] puts stdout "copying layout src/doc files (if target missing)"
puts stdout [punkcheck::summarize_install_resultdict $resultdict] 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. #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. #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*"] ## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"]
set override_antiglob_dir_core [list #* _aside .git] set override_antiglob_dir_core [list #* _aside .git]
puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)" if {[file exists $layout_path/.fossil-custom]} {
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] puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)"
puts stdout [punkcheck::summarize_install_resultdict $resultdict] 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"
}
puts stdout "copying layout src/.fossil-settings files (if target missing or uncustomised)" if {[file exists $layout_path/.fossil-settings]} {
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] puts stdout "copying layout src/.fossil-settings files (if target missing or uncustomised)"
puts stdout [punkcheck::summarize_install_resultdict $resultdict] 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 #scan all files in template
# #
#TODO - deck command to substitute templates? #TODO - deck command to substitute templates?
set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout]
set stripprefix [file normalize $layout_path] set stripprefix [file normalize $layout_path]
@ -399,7 +440,7 @@ namespace eval punk::mix::commandset::project {
if {[llength $templatefiles]} { if {[llength $templatefiles]} {
puts stdout "Filling template file placeholders with the following tag map:" puts stdout "Filling template file placeholders with the following tag map:"
foreach {placeholder value} $tagmap { foreach {placeholder value} $tagmap {
puts stdout " $placeholder -> $value" puts stdout " $placeholder -> $value"
} }
} }
foreach templatefullpath $templatefiles { foreach templatefullpath $templatefiles {
@ -411,7 +452,7 @@ namespace eval punk::mix::commandset::project {
set data2 [string map $tagmap $data] set data2 [string map $tagmap $data]
if {$data2 ne $data} { if {$data2 ne $data} {
puts stdout "updated template file: $fpath" puts stdout "updated template file: $fpath"
set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data2; close $fdout set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data2; close $fdout
} }
} else { } else {
puts stderr "warning: Missing template file $fpath" puts stderr "warning: Missing template file $fpath"
@ -423,7 +464,7 @@ namespace eval punk::mix::commandset::project {
if {[file exists $projectdir/src/modules]} { if {[file exists $projectdir/src/modules]} {
foreach m $opt_modules { foreach m $opt_modules {
#check if mod-ver.tm file or #modpod-mod-ver folder exist #check if mod-ver.tm file or #modpod-mod-ver folder exist
set tmfile $projectdir/src/modules/$m-[punk::mix::util::magic_tm_version].tm set tmfile $projectdir/src/modules/$m-[punk::mix::util::magic_tm_version].tm
set podfile $projectdir/src/modules/#modpod-$m-[punk::mix::util::magic_tm_version]/$m-[punk::mix::util::magic_tm_version].tm set podfile $projectdir/src/modules/#modpod-$m-[punk::mix::util::magic_tm_version]/$m-[punk::mix::util::magic_tm_version].tm
@ -441,7 +482,7 @@ namespace eval punk::mix::commandset::project {
set overwrite_type zip set overwrite_type zip
} else { } else {
set answer [util::askuser "OVERWRITE the src/modules file $tmfile ?? (generally not desirable) Y|N"] set answer [util::askuser "OVERWRITE the src/modules file $tmfile ?? (generally not desirable) Y|N"]
set overwrite_type $opt_type set overwrite_type $opt_type
} }
if {[string tolower $answer] eq "y"} { if {[string tolower $answer] eq "y"} {
#REVIEW - all pods zip - for now #REVIEW - all pods zip - for now
@ -462,7 +503,7 @@ namespace eval punk::mix::commandset::project {
$installer set_source_target $projectdir/src/doc $projectdir/src/embedded $installer set_source_target $projectdir/src/doc $projectdir/src/embedded
set event [$installer start_event {-install_step kettledoc}] set event [$installer start_event {-install_step kettledoc}]
$event targetset_init VIRTUAL kettle_build_doc ;#VIRTUAL - since there is no specific target file - and we don't know all the files that will be generated $event targetset_init VIRTUAL kettle_build_doc ;#VIRTUAL - since there is no specific target file - and we don't know all the files that will be generated
$event targetset_addsource $projectdir/src/doc ;#whole doc tree is considered the source $event targetset_addsource $projectdir/src/doc ;#whole doc tree is considered the source
#---------- #----------
if {\ if {\
[llength [dict get [$event targetset_source_changes] changed]]\ [llength [dict get [$event targetset_source_changes] changed]]\
@ -494,7 +535,7 @@ namespace eval punk::mix::commandset::project {
if {![punk::repo::is_fossil_root $projectdir]} { if {![punk::repo::is_fossil_root $projectdir]} {
set first_fossil 1 set first_fossil 1
#-k = keep. (only modify the manifest file(s)) #-k = keep. (only modify the manifest file(s))
if {$is_nested_fossil} { if {$is_nested_fossil} {
set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir] set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir]
} else { } else {
@ -559,11 +600,11 @@ namespace eval punk::mix::commandset::project {
#[para]The glob argument is optional unless option/value pairs are also supplied, in which case * should be explicitly supplied #[para]The glob argument is optional unless option/value pairs are also supplied, in which case * should be explicitly supplied
#[para]glob restricts output based on the name of the fossil db file e.g s* for all projects beginning with s #[para]glob restricts output based on the name of the fossil db file e.g s* for all projects beginning with s
#[para]The _default function is made available in the ensemble by the name of the prefix used when importing the commandset. #[para]The _default function is made available in the ensemble by the name of the prefix used when importing the commandset.
#[para]e.g #[para]e.g
#[para] punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection #[para] punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
#[para]Will result in the command being available as <ensemblecommand> projects #[para]Will result in the command being available as <ensemblecommand> projects
package require overtype package require overtype
set db_projects [lib::get_projects $glob] set db_projects [lib::get_projects $glob]
set col1items [lsearch -all -inline -index 0 -subindices $db_projects *] set col1items [lsearch -all -inline -index 0 -subindices $db_projects *]
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] set col2items [lsearch -all -inline -index 1 -subindices $db_projects *]
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *]
@ -579,15 +620,15 @@ namespace eval punk::mix::commandset::project {
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]] set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]]
set col3 [string repeat " " $widest3] set col3 [string repeat " " $widest3]
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}] set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}]
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n
append msg [string repeat "=" $tablewidth] \n append msg [string repeat "=" $tablewidth] \n
foreach p $col1items n $col2items c $col3items { foreach p $col1items n $col2items c $col3items {
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n
} }
return $msg return $msg
#return [list_as_lines [lib::get_projects $glob]] #return [list_as_lines [lib::get_projects $glob]]
} }
proc detail {{glob {}} args} { proc detail {{glob {}} args} {
package require overtype package require overtype
@ -599,14 +640,14 @@ namespace eval punk::mix::commandset::project {
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_description [dict get $opts -description] set opt_description [dict get $opts -description]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set db_projects [lib::get_projects $glob]
set db_projects [lib::get_projects $glob]
set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *] set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *]
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] set col2items [lsearch -all -inline -index 1 -subindices $db_projects *]
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *]
set col3items [lmap v $checkouts {llength $v}] set col3items [lmap v $checkouts {llength $v}]
set col4_pnames [list] set col4_pnames [list]
set col5_pcodes [list] set col5_pcodes [list]
set col6_dupids [list] set col6_dupids [list]
@ -617,13 +658,13 @@ namespace eval punk::mix::commandset::project {
set project_name "" set project_name ""
set project_code "" set project_code ""
set project_desc "" set project_desc ""
set db_error "" set db_error ""
if {[file exists $dbfile]} { if {[file exists $dbfile]} {
if {[catch { if {[catch {
sqlite3 dbp $dbfile sqlite3 dbp $dbfile
dbp eval {select name,value from config where name like 'project-%';} r { dbp eval {select name,value from config where name like 'project-%';} r {
if {$r(name) eq "project-name"} { if {$r(name) eq "project-name"} {
set project_name $r(value) set project_name $r(value)
} elseif {$r(name) eq "project-code"} { } elseif {$r(name) eq "project-code"} {
set project_code $r(value) set project_code $r(value)
} elseif {$r(name) eq "project-description"} { } elseif {$r(name) eq "project-description"} {
@ -646,7 +687,7 @@ namespace eval punk::mix::commandset::project {
} }
incr file_idx incr file_idx
} }
set setid 1 set setid 1
set codeset [dict create] set codeset [dict create]
dict for {code dbs} $codes { dict for {code dbs} $codes {
@ -655,17 +696,17 @@ namespace eval punk::mix::commandset::project {
dict set codeset $code count [llength $dbs] dict set codeset $code count [llength $dbs]
dict set codeset $code seen 0 dict set codeset $code seen 0
incr setid incr setid
} }
} }
set dupid 1 set dupid 1
foreach pc $col5_pcodes { foreach pc $col5_pcodes {
if {[dict exists $codeset $pc]} { if {[dict exists $codeset $pc]} {
set seen [dict get $codeset $pc seen] set seen [dict get $codeset $pc seen]
set this_seen [expr {$seen + 1}] set this_seen [expr {$seen + 1}]
dict set codeset $pc seen $this_seen dict set codeset $pc seen $this_seen
lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]" lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]"
} else { } else {
lappend col6_dupids "" lappend col6_dupids ""
} }
} }
@ -691,10 +732,10 @@ namespace eval punk::mix::commandset::project {
#set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {string length $v}]] #set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {string length $v}]]
set widest7 35 set widest7 35
set col7 [string repeat " " $widest7] set col7 [string repeat " " $widest7]
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}] set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}]
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\ append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\
[overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]" [overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]"
if {!$opt_description} { if {!$opt_description} {
@ -706,7 +747,7 @@ namespace eval punk::mix::commandset::project {
append msg [string repeat "=" $tablewidth] \n append msg [string repeat "=" $tablewidth] \n
foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs { foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs {
set desclines [split [textutil::adjust $desc -length $widest7] \n] set desclines [split [textutil::adjust $desc -length $widest7] \n]
set desc1 [lindex $desclines 0] set desc1 [lindex $desclines 0]
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\ append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\
[overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]" [overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]"
@ -715,20 +756,20 @@ namespace eval punk::mix::commandset::project {
} else { } else {
append msg " [overtype::left $col7 $desc1]" \n append msg " [overtype::left $col7 $desc1]" \n
foreach dline [lrange $desclines 1 end] { foreach dline [lrange $desclines 1 end] {
append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n
} }
} }
} }
return $msg return $msg
#return [list_as_lines [lib::get_projects $glob]] #return [list_as_lines [lib::get_projects $glob]]
} }
proc cd {{glob {}} args} { proc cd {{glob {}} args} {
dict set args -cd 1 dict set args -cd 1
work $glob {*}$args work $glob {*}$args
} }
proc work {{glob {}} args} { proc work {{glob {}} args} {
package require sqlite3 package require sqlite3
set db_projects [lib::get_projects $glob] set db_projects [lib::get_projects $glob]
if {[llength $db_projects] == 0} { if {[llength $db_projects] == 0} {
puts stderr "::punk::mix::commandset::project::work No Repo DB name matches found for '$glob'" puts stderr "::punk::mix::commandset::project::work No Repo DB name matches found for '$glob'"
return "" return ""
@ -738,22 +779,22 @@ namespace eval punk::mix::commandset::project {
set defaults [dict create\ set defaults [dict create\
-cd 0\ -cd 0\
-detail "\uFFFF"\ -detail "\uFFFF"\
] ]
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_cd [dict get $opts -cd] set opt_cd [dict get $opts -cd]
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set opt_detail [dict get $opts -detail] set opt_detail [dict get $opts -detail]
set opt_detail_explicit_zero 1 ;#default assumption only set opt_detail_explicit_zero 1 ;#default assumption only
if {$opt_detail eq "\uFFFF"} { if {$opt_detail eq "\uFFFF"} {
set opt_detail_explicit_zero 0 set opt_detail_explicit_zero 0
set opt_detail 0; #default set opt_detail 0; #default
} }
# -- --- --- --- --- --- --- # -- --- --- --- --- --- ---
set workdir_dict [dict create] set workdir_dict [dict create]
set all_workdirs [list] set all_workdirs [list]
foreach pinfo $db_projects { foreach pinfo $db_projects {
lassign $pinfo fosdb name workdirs lassign $pinfo fosdb name workdirs
foreach wdir $workdirs { foreach wdir $workdirs {
dict set workdir_dict $wdir $pinfo dict set workdir_dict $wdir $pinfo
lappend all_workdirs $wdir lappend all_workdirs $wdir
@ -767,15 +808,15 @@ namespace eval punk::mix::commandset::project {
set col_pcodes [list] set col_pcodes [list]
set col_dupids [list] set col_dupids [list]
set fosdb_count [dict create] set fosdb_count [dict create]
set fosdb_dupset [dict create] set fosdb_dupset [dict create]
set fosdb_cache [dict create] set fosdb_cache [dict create]
set dupset 0 set dupset 0
set rowid 1 set rowid 1
foreach wd $workdirs { foreach wd $workdirs {
set wdinfo [dict get $workdir_dict $wd] set wdinfo [dict get $workdir_dict $wd]
lassign $wdinfo fosdb nm siblingworkdirs lassign $wdinfo fosdb nm siblingworkdirs
dict incr fosdb_count $fosdb dict incr fosdb_count $fosdb
set dbcount [dict get $fosdb_count $fosdb] set dbcount [dict get $fosdb_count $fosdb]
if {[llength $siblingworkdirs] > 1} { if {[llength $siblingworkdirs] > 1} {
if {![dict exists $fosdb_dupset $fosdb]} { if {![dict exists $fosdb_dupset $fosdb]} {
@ -784,7 +825,7 @@ namespace eval punk::mix::commandset::project {
} }
set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]" set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]"
} else { } else {
set dupid "" set dupid ""
} }
if {$dbcount == 1} { if {$dbcount == 1} {
set pname "" set pname ""
@ -801,7 +842,7 @@ namespace eval punk::mix::commandset::project {
puts stderr "!!! error: $errM" puts stderr "!!! error: $errM"
} }
} else { } else {
puts stderr "!!! missing fossil db $fosdb" puts stderr "!!! missing fossil db $fosdb"
} }
} else { } else {
set info [dict get $fosdb_cache $fosdb] set info [dict get $fosdb_cache $fosdb]
@ -817,7 +858,7 @@ namespace eval punk::mix::commandset::project {
set col_states [list] set col_states [list]
set state_title "" set state_title ""
#if only one set of fossil checkouts in the resultset and opt_detail is 0 and not explicit - retrieve workingdir state for each co #if only one set of fossil checkouts in the resultset and opt_detail is 0 and not explicit - retrieve workingdir state for each co
if {([llength [dict keys $fosdb_cache]] == 1)} { if {([llength [dict keys $fosdb_cache]] == 1)} {
if {!$opt_detail_explicit_zero} { if {!$opt_detail_explicit_zero} {
set opt_detail 1 set opt_detail 1
@ -843,13 +884,13 @@ namespace eval punk::mix::commandset::project {
set state_dict [punk::repo::workingdir_state_summary_dict $wd_state] set state_dict [punk::repo::workingdir_state_summary_dict $wd_state]
lappend c_rev [string range [dict get $state_dict revision] 0 9] lappend c_rev [string range [dict get $state_dict revision] 0 9]
lappend c_rev_iso [dict get $state_dict revision_iso8601] lappend c_rev_iso [dict get $state_dict revision_iso8601]
lappend c_unchanged [dict get $state_dict unchanged] lappend c_unchanged [dict get $state_dict unchanged]
lappend c_changed [dict get $state_dict changed] lappend c_changed [dict get $state_dict changed]
lappend c_new [dict get $state_dict new] lappend c_new [dict get $state_dict new]
lappend c_missing [dict get $state_dict missing] lappend c_missing [dict get $state_dict missing]
lappend c_extra [dict get $state_dict extra] lappend c_extra [dict get $state_dict extra]
puts -nonewline stderr "." puts -nonewline stderr "."
} }
puts -nonewline stderr \n puts -nonewline stderr \n
set t0 "Revision" set t0 "Revision"
set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]] set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]]
@ -872,13 +913,13 @@ namespace eval punk::mix::commandset::project {
set t5 "Extr" set t5 "Extr"
set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]] set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]]
set c5 [string repeat " " $w5] set c5 [string repeat " " $w5]
set state_title "[overtype::left $c0 $t0] [overtype::left $c0b $t0b] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]" set state_title "[overtype::left $c0 $t0] [overtype::left $c0b $t0b] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]"
foreach r $c_rev iso $c_rev_iso u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra { foreach r $c_rev iso $c_rev_iso u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra {
lappend col_states "[overtype::left $c0 $r] [overtype::left $c0b $iso] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]" lappend col_states "[overtype::left $c0 $r] [overtype::left $c0b $iso] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]"
} }
} }
set msg "" set msg ""
if {$opt_cd} { if {$opt_cd} {
set title0 "CD" set title0 "CD"
@ -907,7 +948,7 @@ namespace eval punk::mix::commandset::project {
append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]" append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]"
if {[llength $col_states]} { if {[llength $col_states]} {
set title6 $state_title set title6 $state_title
set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]] set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]]
set col6 [string repeat " " $widest6] set col6 [string repeat " " $widest6]
incr tablewidth [expr {$widest6 + 1}] incr tablewidth [expr {$widest6 + 1}]
@ -919,12 +960,20 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }
set numrows [llength $col_rowids] set numrows [llength $col_rowids]
if {$opt_cd && $numrows >= 1} { if {$opt_cd && $numrows >= 1} {
@ -936,7 +985,7 @@ namespace eval punk::mix::commandset::project {
::cd $workingdir ::cd $workingdir
return $workingdir return $workingdir
} else { } else {
puts stderr "path $workingdir doesn't appear to exist" puts stderr "path $workingdir doesn't appear to exist"
return [pwd] return [pwd]
} }
} else { } else {
@ -955,12 +1004,12 @@ namespace eval punk::mix::commandset::project {
#*** !doctools #*** !doctools
#[list_end] [comment {-- end collection namespace definitions --}] #[list_end] [comment {-- end collection namespace definitions --}]
} }
namespace eval lib { namespace eval lib {
proc template_tag {tagname} { proc template_tag {tagname} {
#todo - support different tagwrappers - it shouldn't be so likely to collide with common code idioms etc. #todo - support different tagwrappers - it shouldn't be so likely to collide with common code idioms etc.
#we need to detect presence of tags intended for punk::mix system #we need to detect presence of tags intended for punk::mix system
#consider using punk::cap to enable multiple template-substitution providers with their own set of tagnames and/or tag wrappers, where substitution providers are all run #consider using punk::cap to enable multiple template-substitution providers with their own set of tagnames and/or tag wrappers, where substitution providers are all run
return [string cat % $tagname %] return [string cat % $tagname %]
} }
#get project info only by opening the central confg-db #get project info only by opening the central confg-db
@ -983,12 +1032,13 @@ namespace eval punk::mix::commandset::project {
set path [string trim [string range $pr 5 end]] set path [string trim [string range $pr 5 end]]
set nm [file rootname [file tail $path]] set nm [file rootname [file tail $path]]
set ckouts [fosconf eval {select name from global_config where value = $path;}] set ckouts [fosconf eval {select name from global_config where value = $path;}]
#list of entries like "ckout:C:/buildtcl/2024zig/tcl90/"
set checkout_paths [list] set checkout_paths [list]
#strip "ckout:" #strip "ckout:"
foreach ck $ckouts { foreach ck $ckouts {
lappend checkout_paths [string trim [string range $ck 6 end]] lappend checkout_paths [string trim [string range $ck 6 end]]
} }
lappend paths_and_names [list $path $nm $checkout_paths] lappend paths_and_names [list $path $nm $checkout_paths]
} }
set filtered_list [list] set filtered_list [list]
foreach glob $globlist { foreach glob $globlist {
@ -996,16 +1046,14 @@ namespace eval punk::mix::commandset::project {
foreach m $matches { foreach m $matches {
if {$m ni $filtered_list} { if {$m ni $filtered_list} {
lappend filtered_list $m lappend filtered_list $m
} }
} }
} }
set projects [lsort -index 1 $filtered_list] set projects [lsort -index 1 $filtered_list]
return $projects return $projects
} }
} }
@ -1018,15 +1066,10 @@ namespace eval punk::mix::commandset::project {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project { package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project {
variable version variable version
set version 0.1.0 set version 0.1.0
}] }]
return return

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

Loading…
Cancel
Save