Browse Source

update src/project_layouts

master
Julian Noble 1 year ago
parent
commit
837631fa0d
  1. 326
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  2. 2
      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. 705
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/modpod-0.1.0.tm
  5. 1894
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/natsort-0.1.1.5.tm
  6. 3
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  7. 65
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  8. 5
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  9. 20
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  10. 6
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  11. 10
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  12. 26
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm
  13. 7
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/templates/utility/a b/tcltest.bat
  14. 7
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mod-0.1.tm
  15. 21
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  16. 132
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  17. 114
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  18. BIN
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.1.tm
  19. BIN
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.3.tm
  20. 7408
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.1.tm
  21. 160
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/tomlish-1.1.2.tm
  22. 6002
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/tomlish-1.1.3.tm
  23. 326
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  24. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/commandstack-0.3.tm
  25. 21
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm
  26. 3
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  27. 65
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  28. 5
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  29. 20
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  30. 6
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  31. 10
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  32. 26
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm
  33. 7
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mod-0.1.tm
  34. 21
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  35. 132
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  36. 114
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  37. BIN
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.1.tm
  38. BIN
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.3.tm
  39. 160
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/tomlish-1.1.2.tm
  40. 6002
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/tomlish-1.1.3.tm
  41. 326
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl
  42. 259
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/argp-0.2.tm
  43. 568
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/argparsingtest-0.1.0.tm
  44. 514
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/commandstack-0.3.tm
  45. 306
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/debug-1.0.6.tm
  46. 29
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fauxlink-0.1.1.tm
  47. 74
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fileutil/paths-1.tm
  48. 504
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm
  49. 2714
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/flagfilter-0.3.tm
  50. 325
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/funcl-0.1.tm
  51. 1297
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/logger-0.9.5.tm
  52. 6411
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/metaface-1.2.5.tm
  53. 705
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/modpod-0.1.0.tm
  54. 37
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/modpod-0.1.2.tm
  55. 33
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm
  56. 2707
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  57. 1285
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/pattern-1.2.4.tm
  58. 645
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patterncmd-1.2.4.tm
  59. 2590
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patternlib-1.2.6.tm
  60. 754
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/patternpredator2-1.2.4.tm
  61. 1311
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/promise-1.2.0.tm
  62. 8187
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk-0.1.tm
  63. 290
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  64. 1824
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  65. 5307
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  66. 6
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/assertion-0.1.0.tm
  67. 86
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  68. 361
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  69. 487
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/config-0.1.tm
  70. 1516
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  71. 1
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/docgen-0.1.0.tm
  72. 403
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/du-0.1.0.tm
  73. 35
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm
  74. 1857
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  75. 74
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  76. 280
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  77. 1128
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.tm
  78. 42
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm
  79. 118
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm
  80. 41
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  81. 126
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  82. 93
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  83. 27
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm
  84. 100
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  85. BIN
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/templates/modpod/template_modpod-0.0.1/test.zip
  86. 2
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/templates/modules/modulename_buildversion.txt
  87. 4
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/util-0.1.0.tm
  88. 164
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mod-0.1.tm
  89. 1491
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  90. 1492
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm
  91. 4
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/overlay-0.1.tm
  92. 420
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm
  93. 534
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  94. 853
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/pipe-1.0.tm
  95. 276
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.0.tm
  96. 321
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm
  97. 382
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  98. 11
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/tdl-0.1.0.tm
  99. 605
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/trie-0.1.0.tm
  100. 237
      src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/unixywindows-0.1.0.tm
  101. Some files were not shown because too many files have changed in this diff Show More

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

@ -2,6 +2,9 @@
# #
# 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"
@ -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] {
@ -282,7 +285,6 @@ 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
@ -293,6 +295,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
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
@ -307,6 +311,7 @@ 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-"]\
@ -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
@ -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."

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

@ -259,7 +259,7 @@ namespace eval commandstack {
variable debug variable debug
if $debug { if {$debug} {
if {[dict exists $all_stacks $command]} { if {[dict exists $all_stacks $command]} {
set stack [dict get $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]" puts stderr "(commandstack::rename_command) Subsequent rename of command '$command'. (previous renames: [llength $stack]). Renaming to [dict get $nextinfo next_target]"

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

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

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

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\

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

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

@ -664,7 +664,7 @@ namespace eval punk::mix::commandset::project {
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"} {
@ -1032,6 +1032,7 @@ 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 {
@ -1056,8 +1057,6 @@ namespace eval punk::mix::commandset::project {
} }
@ -1067,11 +1066,6 @@ 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 {

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

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'"

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

@ -76,7 +76,7 @@ namespace eval punk::mod::cli {
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
@ -155,9 +155,8 @@ namespace eval punk::mod::cli {
} }
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

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

@ -128,8 +128,7 @@ 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 {
@ -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 ""} {
@ -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
@ -417,9 +476,17 @@ 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
@ -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
@ -938,12 +1016,12 @@ namespace eval punk::repo {
} }
set closest_fossil [lindex [dict get $root_dict fossil] 0] set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_fossil_len [llength [file split $closest_fossil]] set closest_fossil_len [llength [file split $closest_fossil]]
set closest_git [lindex [dict get $root_dict git] 0] set closest_git [lindex [dict get $root_dict git] 0]
set closest_git_len [llength [file split $closest_git]] set closest_git_len [llength [file split $closest_git]]
set closest_candidate [lindex [dict get $root_dict candidate] 0] set closest_candidate [lindex [dict get $root_dict candidate] 0]
set closest_candidate_len [llength [file split $closest_candidate]] 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

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

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

@ -2,6 +2,9 @@
# #
# 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"
@ -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] {
@ -282,7 +285,6 @@ 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
@ -293,6 +295,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
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
@ -307,6 +311,7 @@ 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-"]\
@ -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
@ -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."

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

@ -259,7 +259,7 @@ namespace eval commandstack {
variable debug variable debug
if $debug { if {$debug} {
if {[dict exists $all_stacks $command]} { if {[dict exists $all_stacks $command]} {
set stack [dict get $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]" puts stderr "(commandstack::rename_command) Subsequent rename of command '$command'. (previous renames: [llength $stack]). Renaming to [dict get $nextinfo next_target]"

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

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

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\

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

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

@ -664,7 +664,7 @@ namespace eval punk::mix::commandset::project {
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"} {
@ -1032,6 +1032,7 @@ 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 {
@ -1056,8 +1057,6 @@ namespace eval punk::mix::commandset::project {
} }
@ -1067,11 +1066,6 @@ 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 {

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

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

@ -76,7 +76,7 @@ namespace eval punk::mod::cli {
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
@ -155,9 +155,8 @@ namespace eval punk::mod::cli {
} }
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

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

@ -128,8 +128,7 @@ 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 {
@ -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 ""} {
@ -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
@ -417,9 +476,17 @@ 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
@ -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
@ -938,12 +1016,12 @@ namespace eval punk::repo {
} }
set closest_fossil [lindex [dict get $root_dict fossil] 0] set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_fossil_len [llength [file split $closest_fossil]] set closest_fossil_len [llength [file split $closest_fossil]]
set closest_git [lindex [dict get $root_dict git] 0] set closest_git [lindex [dict get $root_dict git] 0]
set closest_git_len [llength [file split $closest_git]] set closest_git_len [llength [file split $closest_git]]
set closest_candidate [lindex [dict get $root_dict candidate] 0] set closest_candidate [lindex [dict get $root_dict candidate] 0]
set closest_candidate_len [llength [file split $closest_candidate]] 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

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

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

@ -2,6 +2,9 @@
# #
# 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"
@ -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] {
@ -282,7 +285,6 @@ 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
@ -293,6 +295,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
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
@ -307,6 +311,7 @@ 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-"]\
@ -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
@ -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."

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} {

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

1824
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

5307
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

6
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,7 +18,7 @@
# 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 --}]

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

@ -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]
@ -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,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
@ -313,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]} {
@ -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]
@ -471,10 +490,11 @@ namespace eval punk::cap::handlers::templates {
} }
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]
@ -487,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]
@ -505,11 +526,13 @@ namespace eval punk::cap::handlers::templates {
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?
@ -646,14 +669,15 @@ namespace eval punk::cap::handlers::templates {
#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]
@ -755,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

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

@ -552,13 +552,26 @@ tcl::namespace::eval punk::char {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} }
} else { } else {
#review - use -profile?
proc encodable "s {enc [encoding system]}" { proc encodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]] if {![catch {
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
proc decodable "s {enc [encoding system]}" { proc decodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] if {![catch {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
} }
#-- --- --- --- --- --- --- --- #-- --- --- --- --- --- --- ---
@ -587,6 +600,48 @@ tcl::namespace::eval punk::char {
puts stdout \n puts stdout \n
puts stdout "cursor position immediately after outputing 3 bytes (xyz): $cursorposn" puts stdout "cursor position immediately after outputing 3 bytes (xyz): $cursorposn"
} }
proc test_zalgo {} {
#from: https://github.com/jameslanska/unicode-display-width/blob/5e28d94c75e8c421a87199363b85c90dc37125b8/docs/unicode_background.md
#see: https://lingojam.com/ZalgoText
puts stdout "44 chars long - 9 graphemes - 9 columns wide"
set str "Ẓ̌á̲l͔̝̞̄̑͌g̖̘̘̔̔͢͞͝o̪̔T̢̙̫̈̍͞e̬͈͕͌̏͑x̺̍̓̓ͅ"
}
proc test_zalgo2 {} {
# ------------------------
set str "Z̸̢͉̣͔̭̪͙̖̳̘͈͍̤̩̟͈͈͕̯̅̏̆̓̌́́͌̿̕ͅą̷̦̤̫̩͕̥̐̎̓́́̂̀͆́̔̄̈́̏̌́̆͜͜͝͠l̴̩̙̺͚̟͇͖͔͕̹̟͌̈́̄̇́̉̋̕̚͜͠͠g̸͇̩͔̺̝̗̥̖̙̑̈́̈́̿̾̌͌͊̈̀͊̑̇̑͌̍̅̌͊͜͠͝ǫ̴̢̧͎͔̞̤̻̱̮̬͕̗̭͍̣̠̳̆̀̋̓͒̾̄͜͝͠͝T̴̛͉̬̋́̈́̈́̓͗̍̏̔̈̋̀͐̓̎̅̚̕͠͝͝ê̵̖̖̈͊̔̓̔̐̓̃͊̋͋̎͂͋̕x̴̡͎͖̼͎̦͎̲̪̘̺̯̲̬̮̥̼̰͌̃͜ͅt̶̛̘͎̰̔͐͌́̈́͊͗͌̅͂̐̆͌̀͂͘"
# ------------------------
}
proc test_zalgo3 {} {
# ------------------------
set str "Ẕ̸̢̼̺̜̰̣̣̖̭̜͓͖̺̥̼̠͙͙̟̥̟̠̤̫͍̠̤̮͚̝̜̙͈̦̙̩̹̙̜̩̦͔͕̈̃̅̇̈́͂̇̽̈́́́́̎͊̂̑̋͆̔̾͋̚͜ͅã̵̛̪̽̎̃͑̒̇͋͂̽̃̍͊̀̈̊̊̔̊̆̈́͗͑͗̽̄̋͗̄͌̑͊͝͝͠ͅl̵̢͇̖͉͖̝̹̜̞͓͎͍͈̞̱̙͙̦̪͔̱̮͈͉̼͎̭̝̯͇͚̺̟̱͙̳̰̙͚͖̝̫͙̎̅̃͆̈́̋̌̔̋̋͗̈́̔̐͆̈́̓̾̄̀́̏́͒̆̌͒̈́̈́̾̏̀͜͝g̸̖͂͋̊͗̈́̓̆̈̋̒͐̕o̶̧̢͓̲̗̠̘͕̦̤̹̗͉̰͔̯͓̭̹̻͔͇̯̜̙̍̂̃̃̀̓͌̒͊̊̋̒̿̿̌͐̄͗̾̕͝T̶̛̳̜̰͍̹̻̫̠̱̼̼̙̆̑̾̾͛́́̿͋͌̎̀̀̽̆͌̽̂̈̇̅̇̃́̿͗̾͒̎͊̑̾͝͠ȩ̸̢̨̛̛̛̭͔͎͇̫͎͈̲̻̙͕͎̣͈̩̺̗͖͙͇͌͐̒̎͐̓́̉́͐̓̐͌̅̊͋͑̈́͗͑̏̕͜͜͝ͅx̸̧̧̛̖̣̥̘͎͎̳̭̙̦̝͖̝̮̱̹̯̺̙̩̯̮͖̻̰͓̰̩͇̥̑͌̊̐̉̏͆̓̑̎̊̓͒̂̄̆͆̀̊̄̈̽͐͛̏͊̓̌͑́̎̈́̍̈́̊͗̉̋͆̿̊͘͜͜͝͝ͅͅͅt̵̡̨̜̘͎̦͚̠̗̺͉̼̠̲̠͙̺̹̗̲̏̈́̂̚͜͜͝ͅ"
# ------------------------
}
proc test_farmer {} { proc test_farmer {} {
#an interesting article re grapheme clustering problems in terminals https://mitchellh.com/writing/grapheme-clusters-in-terminals #an interesting article re grapheme clustering problems in terminals https://mitchellh.com/writing/grapheme-clusters-in-terminals
#(similar to the problem with grave accent rendering width that the test_grave proc is written for) #(similar to the problem with grave accent rendering width that the test_grave proc is written for)
@ -607,17 +662,29 @@ tcl::namespace::eval punk::char {
puts stdout "farmer2 with no joiner codes: \\U0001f9d1\\U001f33e : $farmer2" puts stdout "farmer2 with no joiner codes: \\U0001f9d1\\U001f33e : $farmer2"
package require punk::console package require punk::console
puts stdout \n
puts stdout "#2--5---9---C---" puts stdout "#2--5---9---C---"
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line 1G cursor at col1 puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line 1G cursor at col1
puts -nonewline "${farmer1}";set cursorposn [punk::console::get_cursor_pos] puts -nonewline "${farmer1}";set cursorposn [punk::console::get_cursor_pos_list]
puts stdout \n puts stdout "\ncursor position immediately after outputing farmer1 (expecting 1 glyph 2 wide) : cursor at col [lindex $cursorposn 1]"
puts stdout "cursor position immediately after outputing farmer1 (expecting 1 glyph 2 wide) : $cursorposn" if {[lindex $cursorposn 1] eq "3"} {
puts stdout "[a+ green]OK[a]"
} else {
puts stdout "[a+ red]ERR - expected cursor position to be 3 after emitting farmer1[a]"
}
puts stdout "----------------"
puts stdout "#2--5---9---C---" puts stdout "#2--5---9---C---"
puts -nonewline "${farmer2}";set cursorposn [punk::console::get_cursor_pos] puts -nonewline "${farmer2}";set cursorposn [punk::console::get_cursor_pos_list]
puts stdout \n puts stdout "\ncursor position immediately after outputing farmer2 (expecting 2 glyphs 4 wide in total): cursor at col [lindex $cursorposn 1]"
puts stdout "cursor position immediately after outputing farmer2 (expecting 2 glyphs 4 wide in total): $cursorposn" if {[lindex $cursorposn 1] eq "5"} {
puts stdout "[a+ green]OK[a]"
} else {
puts stdout "[a+ red]ERR - expected cursor position to be 5 after emitting farmer2[a]"
}
puts stdout "----------------"
return [list $farmer1 $farmer2] puts "returning farmer1 - should be single glyph"
return $farmer1
} }
#G0 Sets Sequence G1 Sets Sequence Meaning #G0 Sets Sequence G1 Sets Sequence Meaning
@ -1114,7 +1181,7 @@ tcl::namespace::eval punk::char {
} }
puts "ok.. loading" puts "ok.. loading"
set fd [open $file r] set fd [open $file r]
fconfigure $fd -translation binary chan configure $fd -translation binary
set data [read $fd] set data [read $fd]
close $fd close $fd
set block_count 0 set block_count 0
@ -1179,6 +1246,10 @@ tcl::namespace::eval punk::char {
return [charset_dict "Box Drawing"] return [charset_dict "Box Drawing"]
} }
proc char_hex {char} {
return [format %08x [scan $char %c]]
}
proc char_info_hex {hex args} { proc char_info_hex {hex args} {
set hex [tcl::string::map [list _ ""] $hex] set hex [tcl::string::map [list _ ""] $hex]
if {[tcl::string::is xdigit -strict $hex]} { if {[tcl::string::is xdigit -strict $hex]} {
@ -1899,15 +1970,127 @@ tcl::namespace::eval punk::char {
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
# - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth {string} { proc wcswidth {string} {
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] #faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+ (Tcl evaluation stack has to be reallocated/copied?)
#Tcl initial evaluation stack size is 2000 (? review)
#REVIEW - when we cater for grapheme clusters - we can't just split the string at arbitrary points like this!!
set chunksize 2000
set chunks_required [expr {int(ceil([tcl::string::length $string] / double($chunksize)))}]
set width 0 set width 0
foreach c $codes { set startidx 0
if {$c <= 255} { set endidx [expr {$startidx + $chunksize -1}]
incr width for {set i 0} {$i < $chunks_required} {incr i} {
set chunk [tcl::string::range $string $startidx $endidx]
set codes [scan $chunk [tcl::string::repeat %c [tcl::string::length $chunk]]]
foreach c $codes {
if {$c <= 255 && !($c < 31 || $c == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w
}
}
}
incr startidx $chunksize
incr endidx $chunksize
}
return $width
}
# ------------------------------------------------------------------------------------------------------
#test
# ------------------------------------------------------------------------------------------------------
proc grapheme_split_tk {string} {
if {![regexp "\[\uFF-\U10FFFF\]" $string]} {
#only ascii (7 or 8 bit) - no joiners or unicode
return [split $string {}]
}
package require tk
set i 0
set graphemes [list]
while {$i < [tcl::string::length $string]} {
set aftercluster [tk::endOfCluster $string $i]
lappend graphemes [string range $string $i $aftercluster-1]
set i $aftercluster
}
return $graphemes
}
proc wcswidth_clustered {string} {
package require tk
set width 0
set i 0
if {![regexp "\[\uFF-\U10FFFF\]" $string]} {
return [punk::char::wcswidth_unclustered $string] ;#still use our wcswidth to account for non-printable ascii
}
while {$i < [tcl::string::length $string]} {
set aftercluster [tk::endOfCluster $string $i]
set g [string range $string $i $aftercluster-1]
if {$aftercluster > ($i + 1)} {
#review - proper way to determine screen width (columns occupied) of a cluster??
#according to this:
#https://lib.rs/crates/unicode-display-width
#for each grapheme - if any of the code points in the cluster have an east asian width of 2,
#The entire grapheme width is 2 regardless of how many code points constitute the grapheme
set gw 1
foreach ch [split $g ""] {
if {[punk::char::wcswidth_single $ch] == 2} {
set gw 2
break
}
}
incr width $gw
#if {[string first \u200d $g] >=0} {
# incr width 2
#} else {
# #other joiners???
# incr width [wcswidth_unclustered $g]
#}
} else { } else {
set w [textutil::wcswidth_char $c] incr width [wcswidth_unclustered $g]
}
set i $aftercluster
}
return $width
}
proc wcswidth_single {char} {
scan $char %c dec
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
return 1
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
return [textutil::wcswidth_char $dec] ;#note textutil::wcswidth_char takes a decimal codepoint!
#may return -1 - REVIEW
}
return 0
}
proc wcswidth_unclustered1 {string} {
set width 0
foreach c [split $string {}] {
scan $c %c dec
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} { if {$w < 0} {
return -1 return -1
} else { } else {
@ -1917,23 +2100,76 @@ tcl::namespace::eval punk::char {
} }
return $width return $width
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
proc wcswidth1 {string} { # - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth_unclustered {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+ (Tcl evaluation stack has to be reallocated/copied?)
#Tcl initial evaluation stack size is 2000 (? review)
#we can only split the string at arbitrary points like this because we are specifically dealing with input that has no clusters!.
set chunksize 2000
set chunks_required [expr {int(ceil([tcl::string::length $string] / double($chunksize)))}]
set width 0
set startidx 0
set endidx [expr {$startidx + $chunksize -1}]
for {set i 0} {$i < $chunks_required} {incr i} {
set chunk [tcl::string::range $string $startidx $endidx]
set codes [scan $chunk [tcl::string::repeat %c [tcl::string::length $chunk]]]
foreach dec $codes {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $dec]
if {$w < 0} {
return -1
} else {
incr width $w
}
}
}
incr startidx $chunksize
incr endidx $chunksize
}
return $width
}
# ------------------------------------------------------------------------------------------------------
proc wcswidth0 {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0 set width 0
foreach c $codes { foreach dec $codes {
set w [textutil::wcswidth_char $c] #unicode Tags block zero width
if {$w < 0} { if {$dec < 917504 || $dec > 917631} {
return -1 if {$dec <= 255} {
} else { #review - non-printing ascii? why does textutil::wcswidth report 1 ??
incr width $w #todo - compare with python or other lang wcwidth
if {!($dec < 31 || $dec == 127)} {
incr width
}
} else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
incr width $w
}
}
} }
} }
return $width return $width
} }
proc wcswidth2 {string} { proc wcswidth2 {string} {
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set widths [lmap c $codes {textutil::wcswidth_char $c}] set widths [lmap dec $codes {textutil::wcswidth_char $dec}]
if {-1 in $widths} { if {-1 in $widths} {
return -1 return -1
} }
@ -2016,7 +2252,8 @@ tcl::namespace::eval punk::char {
# return [tcl::string::length $text] # return [tcl::string::length $text]
#} #}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} { if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [tcl::string::length $text] #return [tcl::string::length $text]
return [punk::char::wcswidth $text] ;#still use our wcswidth to account for non-printable ascii
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first? #split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
@ -2039,7 +2276,7 @@ tcl::namespace::eval punk::char {
#fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for) #fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for)
#todo - find something that understands grapheme clusters - needed also for grapheme_split #todo - find something that understands grapheme clusters - needed also for grapheme_split
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char #use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [wcswidth $uc] incr len [punk::char::wcswidth $uc]
} }
#todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies. #todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies.
return $len return $len
@ -2216,7 +2453,68 @@ tcl::namespace::eval punk::char {
return [tcl::string::map $map $str] return [tcl::string::map $map $str]
} }
#todo - lookup from unicode tables
variable flags [dict create\
AU \U1F1E6\U1F1FA\
US \U1F1FA\U1F1F8\
ZW \U1F1FF\U1F1FC
]
variable rflags
dict for {k v} $flags {
dict set rflags $v $k
}
proc flag_from_ascii {code} {
variable flags
if {[regexp {^[A-Z]{2}$} $code]} {
if {[dict exists $flags $code]} {
return [dict get $flags $code]
} else {
error "unsupported flags code: $code"
}
} else {
#try as subregion
#e.g gbeng,gbwls,gbsct
return \U1f3f4[tag_from_ascii $code]\Ue007f
}
}
proc flag_to_ascii {charsequence} {
variable rflags
if {[dict exists $rflags $charsequence]} {
return [dict get $rflags $charsequence]
}
if {[string index $charsequence 0] eq "\U1F3F4" && [string index $charsequence end] eq "\UE007F"} {
#subdivision flag
set tag [string range $charsequence 1 end-1]
return [tag_to_ascii $tag]
}
error "unknown flag $charsequence"
}
proc tag_to_ascii {t} {
set fmt [string repeat %c [string length $t]]
set declist [scan $t $fmt]
#unicode Tags block - e0000 to e007f
set declist [lmap dec $declist {
if {$dec < 917504 || $dec > 917631} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in unicode Tags block range 917504-917631 (e0000-e007f)"
}
incr dec -917504
}]
return [format $fmt {*}$declist]
}
proc tag_from_ascii {a} {
set fmt [string repeat %c [string length $a]]
set declist [scan $a $fmt]
set declist [lmap dec $declist {
if {$dec > 127} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in ascii range 0-127"
}
incr dec 917504
}]
return [format $fmt {*}$declist]
}
#split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ) #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
@ -2237,15 +2535,12 @@ tcl::namespace::eval punk::char {
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd] lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}] set start [expr {$matchEnd+1}]
#if {$start >= [tcl::string::length $text]} {
# break
#}
} }
lappend list [tcl::string::range $text $start end] lappend list [tcl::string::range $text $start end]
} }
#ZWJ ZWNJ ? #ZWJ ZWNJ ?
#SWSP ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
@ -2256,6 +2551,8 @@ tcl::namespace::eval punk::char {
#should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters #should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters
#Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs) #Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs)
#This still leaves a whole class of clusters.. korean etc unhandled :/ #This still leaves a whole class of clusters.. korean etc unhandled :/
#todo - tk::startOfCluster / tk::endOfCluster - try to get it brought into Tcl
#https://core.tcl-lang.org/tcl/tktview/a4c7eeaf63
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -2274,7 +2571,7 @@ tcl::namespace::eval punk::char {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] { foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] ;#warning scan %c... slow for v large strings (e.g 400k+)
set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]] set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs] lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs lappend graphemes {*}$pt_decs

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

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

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

@ -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]
@ -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 {
@ -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
} }
@ -1252,6 +1254,16 @@ namespace eval 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]]
@ -1267,14 +1279,7 @@ namespace eval punk::fileline {
#[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]
@ -1285,7 +1290,7 @@ 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
@ -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 {
@ -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]
@ -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
} }

1857
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

74
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 {
@ -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]
@ -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
} }
@ -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]
} }
@ -655,16 +674,38 @@ namespace eval punk::mix::base {
#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
@ -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 {
@ -810,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
@ -828,7 +871,7 @@ namespace eval punk::mix::base {
#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]} {
@ -870,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]

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

@ -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]
@ -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
@ -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\
@ -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,7 +381,13 @@ 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} {
@ -337,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..
@ -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]
@ -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,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
@ -654,28 +747,37 @@ namespace eval punk::mix::cli {
$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 {
@ -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
@ -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\

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

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

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

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

@ -109,7 +109,26 @@ namespace eval punk::mix::commandset::project {
} }
namespace eval argdoc {
set layout_dict [punk::cap::call_handler punk.templates get_itemdict_projectlayouts]
variable LAYOUTNAMES [dict keys $layout_dict]
}
punk::args::define {
@id -id ::punk::mix::commandset::project::new
@cmd -name "punk::mix::commandset::project::new" -help\
""
@leaders -min 1 -max 1
project -type string -help\
"Project name or path.
If just a name is given ... (todo)"
@opts
-type -default plain
-empty -default 0 -type boolean
-force -default 0 -type boolean
-update -default 0 -type boolean
-confirm -default 1 -type boolean
-layout -default "punk.project" -choices {${$::punk::mix::commandset::project::argdoc::LAYOUTNAMES}}
}
proc new {newprojectpath_or_name args} { proc new {newprojectpath_or_name args} {
#*** !doctools #*** !doctools
@ -157,6 +176,9 @@ 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
@ -165,13 +187,10 @@ namespace eval punk::mix::commandset::project {
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list $projectname] set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- ---
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
@ -300,7 +319,17 @@ 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 ""
@ -366,28 +395,40 @@ namespace eval punk::mix::commandset::project {
#default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized #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
# #
@ -623,7 +664,7 @@ namespace eval punk::mix::commandset::project {
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"} {
@ -919,10 +960,18 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }
@ -983,6 +1032,7 @@ 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 {
@ -1007,8 +1057,6 @@ namespace eval punk::mix::commandset::project {
} }
@ -1018,11 +1066,6 @@ 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 {

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

@ -24,7 +24,11 @@
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
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
set result "" set result ""
if {[string length $project]} { if {[string length $project]} {
puts stderr "project status unimplemented" puts stderr "project status unimplemented"
@ -51,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"
@ -68,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]
@ -401,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
}

100
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/commandset/scriptwrap-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 'dev 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.
@ -13,19 +13,70 @@
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_scriptwrap 0 0.1.0]
#[copyright "2024"]
#[titledesc {scriptwrap polyglot tool}] [comment {-- Name section and table of contents description --}]
#[moddesc {scriptwrap tool}] [comment {-- Description at end of page heading --}]
#[require punk::mix::commandset::scriptwrap]
#[keywords module commandset launcher scriptwrap]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of scriptwrap
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by poshinfo
#[list_begin itemized]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz ##e.g package require frobz
package require punk::lib
package require punk::args
package require punk::mix package require punk::mix
package require punk::mix::base package require punk::mix::base
package require punk::fileline package require punk::fileline
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {punk::lib}]
#[item] [package {punk::args}]
#[item] [package {punk::mix}]
#[item] [package {punk::base}]
#[item] [package {punk::fileline}]
#*** !doctools
#[list_end]
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::mix::commandset::scriptwrap { namespace eval punk::mix::commandset::scriptwrap {
#*** !doctools
#[subsection {Namespace punk::mix::commandset::scriptwrap}]
#[para] Core API functions for punk::mix::commandset::scriptwrap
#[list_begin definitions]
namespace export * namespace export *
namespace eval fileline { namespace eval fileline {
@ -1192,23 +1243,34 @@ namespace eval punk::mix::commandset::scriptwrap {
return $result return $result
} }
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::mix::commandset::scriptwrap ---}]
namespace eval lib { namespace eval lib {
#*** !doctools
#[subsection {Namespace punk::mix::commandset::scriptwrap::lib}]
#[para] Library API functions for punk::mix::commandset::scriptwrap
#[list_begin definitions]
proc get_wrapper_folders {args} { proc get_wrapper_folders {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
#*** !doctools #*** !doctools
#[call [fun get_wrapper_folders] [arg args] ] #[call [fun get_wrapper_folders] [arg args] ]
#[para] Return list of dicts representing wrapper folders. keys: basefolder sourceinfo #[para] Return list of dicts representing wrapper folders in the project. keys: basefolder sourceinfo
#[para] Arguments: #[para] Arguments:
# [list_begin arguments] # [list_begin arguments]
# [arg_def string args] name-value pairs -scriptpath <path> # [arg_def string args] name-value pairs -scriptpath <path>
# [list_end] # [list_end]
*proc -name get_wrapper_folders @id -id ::punk::mix::commandset::scriptwrap
*opts -anyopts 0 @cmd -name punk::mix::commandset::get_wrapper_folders
-scriptpath -default ""
*values -minvalues 0 -maxvalues 0 @opts -anyopts 0
-scriptpath -default "" -type directory\
-help ""
#todo -help folder within a punk.templates provided area???
@values -minvalues 0 -maxvalues 0
} $args] } $args]
# -- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- ---
@ -1377,11 +1439,16 @@ namespace eval punk::mix::commandset::scriptwrap {
return [dict create ok $status linecount [llength $lines] data $tags errors $errors] return [dict create ok $status linecount [llength $lines] data $tags errors $errors]
} }
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::mix::commandset::scriptwrap::lib ---}]
} }
namespace eval batchlib { namespace eval batchlib {
# #*** !doctools
#[subsection {Namespace punk::mix::commandset::scriptwrap::batchlib}]
#[para] Utility funcions for processing windows .bat files
#[list_begin definitions]
#see also: https://www.dostips.com/forum/viewtopic.php?t=3803 'Rules for label names vs GOTO and CALL #see also: https://www.dostips.com/forum/viewtopic.php?t=3803 'Rules for label names vs GOTO and CALL
# review - we may need different get_callsite_label functions? # review - we may need different get_callsite_label functions?
@ -1647,23 +1714,13 @@ namespace eval punk::mix::commandset::scriptwrap {
#return rawlabel so we can see it as it appears in the data - as opposed to how it is interpreted as a label by cmd.exe #return rawlabel so we can see it as it appears in the data - as opposed to how it is interpreted as a label by cmd.exe
return [list labelfound 1 label $label rawlabel $rawlabel] return [list labelfound 1 label $label rawlabel $rawlabel]
} }
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::mix::commandset::scriptwrap::batchlib ---}]
} }
} }
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::mix::commandset::scriptwrap [namespace eval punk::mix::commandset::scriptwrap { package provide punk::mix::commandset::scriptwrap [namespace eval punk::mix::commandset::scriptwrap {
@ -1671,3 +1728,6 @@ package provide punk::mix::commandset::scriptwrap [namespace eval punk::mix::com
set version 0.1.0 set version 0.1.0
}] }]
return return
#*** !doctools
#[manpage_end]

BIN
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/templates/modpod/template_modpod-0.0.1/test.zip vendored

Binary file not shown.

2
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/templates/modules/modulename_buildversion.txt vendored

@ -1,3 +1,3 @@
%Major.Minor.Level% %Major.Minor.Level%
#First line must be a semantic version number #First line must be a tcl package version number
#all other lines are ignored. #all other lines are ignored.

4
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/mix/util-0.1.0.tm vendored

@ -51,7 +51,7 @@ namespace eval punk::mix::util {
} else { } else {
if {$ival in $knownopts} { if {$ival in $knownopts} {
#puts ">known at $i : [lindex $args $i]" #puts ">known at $i : [lindex $args $i]"
if {($i % 2) != 0} { if {$i % 2} {
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs."
} }
incr i incr i
@ -261,6 +261,8 @@ namespace eval punk::mix::util {
return return
} }
# review punk::lib::tm_version.. functions
proc is_valid_tm_version {versionpart} { proc is_valid_tm_version {versionpart} {
#Needs to be suitable for use with Tcl's 'package vcompare' #Needs to be suitable for use with Tcl's 'package vcompare'
if {![catch [list package vcompare $versionpart $versionpart]]} { if {![catch [list package vcompare $versionpart $versionpart]]} {

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

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

1491
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm vendored

File diff suppressed because it is too large Load Diff

1492
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm vendored

File diff suppressed because it is too large Load Diff

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

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

420
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm vendored

@ -0,0 +1,420 @@
# -*- 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.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::packagepreference 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::packagepreference 0 0.1.0]
#[copyright "2024"]
#[titledesc {punkshell package/module loading}] [comment {-- Name section and table of contents description --}]
#[moddesc {package/module load}] [comment {-- Description at end of page heading --}]
#[require punk::packagepreference]
#[keywords module package]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::packagepreference
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::packagepreference
#[list_begin itemized]
package require Tcl 8.6-
package require commandstack
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {commandstack}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::packagepreference::class {
#*** !doctools
#[subsection {Namespace punk::packagepreference::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::packagepreference {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
#*** !doctools
#[subsection {Namespace punk::packagepreference}]
#[para] Core API functions for punk::packagepreference
#[list_begin definitions]
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc uninstall {} {
#*** !doctools
#[call [fun uninstall]]
#[para]Return to the previous ::package implementation (This will be the builtin if no other override was present when install was called)
commandstack::remove_rename {::package punk::packagepreference}
}
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc install {} {
#*** !doctools
#[call [fun install]]
#[para]Override ::package builtin (or the current implementation if it has already been renamed/overridden) to check for and prefer lowercase packages/modules
#[para](todo - check info loaded and restrict to existing version as determined from dll/so?)
#[para]The overriding package command will call whatever implementation was in place before install to do the actual work - once it has modified 'package require' names to lowercase.
#[para]This is intended to be in alignment with tip 590 "Recommend lowercase Package Names"
#[para] https://core.tcl-lang.org/tips/doc/trunk/tip/590.md
#[para]It prevents some loading errors when multiple package versions are available to an interpreter and the latest version is only provided by a lowercased module (.tm file)
#[para]A package provided by the standard pkgIndex.tcl mechanism might override a later-versioned package because it may support both upper and lowercased names.
#[para]The overriding of ::package only looks at 'package require' calls and preferentially tries the lowercase version (if the package isn't already loaded with either upper or lowercase name)
#[para]This comes at some slight cost for packages that are only available with uppercase letters in the name - but at minimal cost for recommended lowercase package names
#[para]Return to the standard ::package builtin by calling punk::packagepreference::uninstall
#todo - review/update commandstack package
#modern module/lib names should preferably be lower case
#see tip 590 - "Recommend lowercase Package names". Where non-lowercase are deprecated (but not removed even in Tcl9)
#Mixed case causes problems esp on windows where we can't have Package.tm and package.tm as they aren't distinguishable.
#We enforce package to try lowercase first - at some potentially significant speed penalty if the lib actually does use uppercase
#(also just overloading the package builtin comes at a cost!)
#Without the lowercase-first mechanism - a lower versioned package such as Tablelist provided by a pkgIndex.tcl may be loaded in precedence to a higher versioned tablelist provided by a .tm
#As punk kits launched with dev first arg may use tcl::tm::paths coming from both the system and zipkits for example - this is a problem.
#(or in any environment where multiple versions of Tcl libraries may be available)
#We can't provide a modernised .tm package for an old package that is commonly used with uppercase letters, in both the old form and lowercased form from a single .tm file.
#It could be done by providing a redirecting .tm in a separate module path, or by providing a packageIndex.tcl to redirect it but both these solutions are error prone from a sysops perspective.
set stackrecord [commandstack::rename_command -renamer punk::packagepreference package {args} {
#::package override installed by punk::packagepreference::install
#return to previous 'package' implementation with: punk::packagepreference::uninstall
#uglier but faster than tcl::prefix::match in this instance
#maintenance - check no prefixes of require are added to builtin package command
switch -exact -- [lindex $args 0] {
r - re - req - requi - requir - require {
#puts "==>package $args"
#puts "==>[info level 1]"
#despite preference for lowercase - we need to handle packages that insist on providing as uppercase
#(e.g we will still need to handle things like: package provide Tcl 8.6)
#Where the package is already provided uppercase we shouldn't waste time deferring to lowercase
set is_exact 0
if {[lindex $args 1] eq "-exact"} {
set pkg [lindex $args 2]
set vwant [lindex $args 3]-[lindex $args 3]
set is_exact 1
} else {
set pkg [lindex $args 1]
set vwant [lrange $args 2 end] ;#rare - but version can be a list of options
if {[llength $vwant] == 1 && [string first - [lindex $vwant 0]] > 0} {
#only one version - and it has a dash
lassign [split [lindex $vwant 0] -] a b
if {$a eq $b} {
#string compare version nums (can contain dots and a|b)
set is_exact 1
}
}
}
if {[set ver [$COMMANDSTACKNEXT_ORIGINAL provide $pkg]] ne ""} {
#although we could shortcircuit using vsatisfies to return the ver
#we should instead pass through to COMMANDSTACKNEXT so as not to interfere with whatever it does.
#e.g a package require logger further down the commandstack
return [$COMMANDSTACKNEXT {*}$args]
}
if {!$is_exact && [llength $vwant] <= 1 } {
#required version unspecified - or specified singularly
set available_versions [$COMMANDSTACKNEXT_ORIGINAL versions $pkg]
if {[llength $available_versions] > 1} {
# ---------------------------------------------------------------
#An attempt to detect dll/so loaded and try to load same version
#dll/so files are often named with version numbers that don't contain dots or a version number at all
#e.g sqlite3400.dll Thread288.dll
set pkgloadedinfo [lsearch -nocase -inline -index 1 [info loaded] $pkg]
if {[llength $pkgloadedinfo]} {
puts stderr "--> pkg not already 'provided' but shared object seems to be loaded: $pkgloadedinfo - and multiple versions available"
lassign $pkgloadedinfo path name
set lcpath [string tolower $path]
#first attempt to find a match for our loaded sharedlib path in a *simple* package ifneeded statement.
set lcpath_to_version [dict create]
foreach av $available_versions {
set scr [package ifneeded $pkg $av]
#ifneeded script not always a valid tcl list
if {![catch {llength $scr} scrlen]} {
if {$scrlen == 3 && [lindex $scr 0] eq "load" && [string match -nocase [lindex $scr 2] $pkg]} {
dict set lcpath_to_version [string tolower [lindex $scr 1]] $av
}
}
}
if {[dict exists $lcpath_to_version $lcpath]} {
set lversion [dict get $lcpath_to_version $lcpath]
} else {
#fallback to a best effort guess based on the path
set lversion [::punk::packagepreference::system::slibpath_guess_pkgversion $path $pkg]
}
if {$lversion ne ""} {
#name matches pkg
#hack for known dll version mismatch
if {[string tolower $pkg] eq "thread" && $lversion eq "30b3"} {
set lversion 3.0b3
}
if {[llength $vwant] == 1} {
#todo - still check vsatisfies - report a conflict? review
}
return [$COMMANDSTACKNEXT require $pkg $lversion-$lversion]
}
}
}
}
# ---------------------------------------------------------------
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] $pkg]
if {[regexp {[A-Z]} $pkg]} {
#only apply catch & retry if there was a cap - otherwise we'll double try for errors unrelated to capitalisation
if {[catch {$COMMANDSTACKNEXT require [string tolower $pkg] {*}$vwant} v]} {
return [$COMMANDSTACKNEXT require $pkg {*}$vwant]
} else {
return $v
}
} else {
return [$COMMANDSTACKNEXT require $pkg {*}$vwant]
}
}
default {
return [$COMMANDSTACKNEXT {*}$args]
}
}
}]
if {[dict get $stackrecord implementation] ne ""} {
set impl [dict get $stackrecord implementation] ;#use hardcoded name rather than slower (but more flexible) commandstack::get_next_command
#puts stdout "punk::packagepreference renamed ::package to $impl"
return 1
} else {
puts stderr "punk::packagepreference failed to rename ::package"
return 0
}
#puts stdout [info body ::package]
}
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::packagepreference ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::packagepreference::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::packagepreference::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::packagepreference::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
tcl::namespace::eval punk::packagepreference::system {
#*** !doctools
#[subsection {Namespace punk::packagepreference::system}]
#[para] Internal functions that are not part of the API
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::system::slibpath_guess_pkgversion
@cmd -name punk::packagepreference::system::slibpath_guess_pkgversion -help\
"Assistance function to determine pkg version from the information
obtained from [info loaded]. This is used to try to avoid loading a different
version of a binary package in another thread/interp when the package isn't
present in the interp, but [info loaded] indicates the binary is already loaded.
The more general/robust way to avoid this is to ensure ::auto_path and
tcl::tm::list are the same in each interp/thread.
This call should only be used as a fallback in case a binary package has a more
complex ifneeded script. If the ifneeded script for a binary package is a
straightforward 'load <path_to_binary> <pkgname>' - then that information
should be used to determine the version by matching <path_to_binary>
rather than this one.
Takes a path to a shared lib (.so/.dll), and the name of its providing
package, and return the version of the package if possible to determine
from the path.
The filename portion of the lib is often missing a version number or has
a version number that has been shortened (e.g dots removed).
The filename itself is first checked for a version number - but the number
is ignored if it doesn't contain any dots.
(prefix is checked to match with $pkgname, with a possible additional prefix
of lib or tcl<int>)
Often (even usually) the parent or grandparent folder will be named as
per the package name with a proper version. If so we can return it,
otherwise return empty string.
The parent/grandparent matching will be done by looking for a case
insensitive match of the prefix to $pkgname.
"
@values -min 1
libpath -help "Full path to shared library (.so,.dll etc)"
pkgname -help ""
}]
proc slibpath_guess_pkgversion {libpath pkgname} {
set root [file rootname [file tail $libpath]]
set namelen [string length $pkgname]
regexp {^(tcl(?:[0-9])+){0,1}(.*)} $root _match tclxx root ;#regexp will match anything - but only truncate leading tclXX..
set testv ""
if {[string match -nocase $pkgname* $root]} {
set testv [string range $root $namelen end]
} elseif {[string match -nocase lib$pkgname* $root]} {
set testv [string range $root $namelen+3 end]
}
if {[string first . $testv] > 0} {
if {![catch [list package vcompare $testv $testv]]} {
#testv has an inner dot and is understood by tcl as a valid version number
return $testv
}
}
#no valid dotted version found directly on dll or so filename
set parent [file dirname $libpath] ;#parent folder is often some differentiator for platform or featureset (e.g win-x64)
set grandparent [file dirname $parent]
foreach path [list $parent $grandparent] {
set segment [file tail $path]
if {$segment eq "bin"} {
continue
}
set testv ""
if {[string match -nocase $pkgname* $segment]} {
set testv [string range $segment $namelen end]
} elseif {[string match -nocase critcl_$pkgname* $segment]} {
set testv [string range $segment $namelen+7 end]
}
#we don't look for dot in parent/grandparent version - a bare integer here after the <pkgname> will be taken to be the version
if {![catch [list package vcompare $testv $testv]]} {
return $testv
}
}
#review - sometimes path and lib are named only for major.minor but package provides major.minor.subversion
#using returned val to attempt to package require -exact major.minor will fail to load major.minor.subversion
return ""
}
}
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::packagepreference ::punk::packagepreference::system
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::packagepreference [tcl::namespace::eval punk::packagepreference {
variable pkg punk::packagepreference
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

534
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm vendored

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

853
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/pipe-1.0.tm vendored

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

276
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.0.tm vendored

@ -0,0 +1,276 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.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::repl::codethread 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::repl::codethread 0 0.1.0]
#[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
#[require punk::repl::codethread]
#[keywords module repl]
#[description]
#[para] This is part of the infrastructure required for the punk::repl to operate
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::repl::codethread
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::repl::codethread
#[list_begin itemized]
package require Tcl 8.6-
package require punk::config
#*** !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::repl::codethread::class {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread {
tcl::namespace::export *
variable replthread
variable replthread_cond
variable running 0
variable output_stdout ""
variable output_stderr ""
#variable xyz
#*** !doctools
#[subsection {Namespace punk::repl::codethread}]
#[para] Core API functions for punk::repl::codethread
#[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"
#}
variable run_command_cache
proc is_running {} {
variable running
return $running
}
proc runscript {script} {
#puts stderr "->runscript"
variable replthread_cond
#variable output_stdout
#set output_stdout ""
#variable output_stderr
#set output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} {
#in case someone tries calling from codethread directly - don't do anything or change any state
#(direct caller could create an interp named code at the level "" -> "code" -"code" and add a replthread_cond value to avoid this check - but it probably won't do anything useful)
#if called directly - the context will be within the first 'code' interp.
#inappropriate caller could add superfluous entries to shellfilter stack if function errors out
#inappropriate caller could affect tsv vars (if their interp allows that anyway)
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return
}
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list]
set errstack [list]
upvar ::punk::config::running running_config
if {[string length [dict get $running_config color_stdout_repl]] && [interp eval code punk::console::colour]} {
lappend outstack [interp eval code [list shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout_repl]]]]
}
lappend outstack [interp eval code [list shellfilter::stack::add stdout tee_to_var -settings {-varname ::punk::repl::codethread::output_stdout}]]
if {[string length [dict get $running_config color_stderr_repl]] && [interp eval code punk::console::colour]} {
lappend errstack [interp eval code [list shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr_repl]]]]
# #lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour cyan]]
}
lappend errstack [interp eval code [list shellfilter::stack::add stderr tee_to_var -settings {-varname ::punk::repl::codethread::output_stderr}]]
#an experiment
#set errhandle [shellfilter::stack::item_tophandle stderr]
#interp transfer "" $errhandle code
set status [catch {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
} result]
flush stdout
flush stderr
#interp transfer code $errhandle ""
#flush $errhandle
set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end]
set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end]
#puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]"
set tid [thread::id]
tsv::set codethread_$tid info [list lastoutchar $lastoutchar lasterrchar $lasterrchar]
tsv::set codethread_$tid status $status
tsv::set codethread_$tid result $result
tsv::set codethread_$tid errorcode $::errorCode
#only remove from shellfilter::stack the items we added to stack in this function
foreach s [lreverse $outstack] {
interp eval code [list shellfilter::stack::remove stdout $s]
}
foreach s [lreverse $errstack] {
interp eval code [list shellfilter::stack::remove stderr $s]
}
thread::cond notify $replthread_cond
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread::lib {
tcl::namespace::export *
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::repl::codethread::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::repl::codethread::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
tcl::namespace::eval punk::repl::codethread::system {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::repl::codethread [tcl::namespace::eval punk::repl::codethread {
variable pkg punk::repl::codethread
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

321
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm vendored

@ -0,0 +1,321 @@
# -*- 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::repl::codethread 0.1.1
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::repl::codethread 0 0.1.1]
#[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
#[require punk::repl::codethread]
#[keywords module repl]
#[description]
#[para] This is part of the infrastructure required for the punk::repl to operate
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::repl::codethread
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::repl::codethread
#[list_begin itemized]
package require Tcl 8.6-
package require punk::config
#*** !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::repl::codethread::class {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread {
tcl::namespace::export *
variable replthread
variable replthread_cond
variable running 0
variable output_stdout ""
variable output_stderr ""
#review/test
catch {package require punk::ns}
catch {package rquire punk::repl}
#variable xyz
#*** !doctools
#[subsection {Namespace punk::repl::codethread}]
#[para] Core API functions for punk::repl::codethread
#[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"
#}
variable run_command_cache
#Use interp exists instead..
#if {[catch {interp children}]} {
# #8.6.10 doesn't have it.. when was it introduced?
#} else {
#}
proc is_running {} {
variable running
return $running
}
proc runscript {script} {
#puts stderr "->runscript"
variable replthread_cond
#variable output_stdout ""
#variable output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will
if {![interp exists code] || ![info exists replthread_cond]} {
#in case someone tries calling from codethread directly - don't do anything or change any state
#(direct caller could create an interp named code at the level "" -> "code" -"code" and add a replthread_cond value to avoid this check - but it probably won't do anything useful)
#if called directly - the context will be within the first 'code' interp.
#inappropriate caller could add superfluous entries to shellfilter stack if function errors out
#inappropriate caller could affect tsv vars (if their interp allows that anyway)
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return
}
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list]
set errstack [list]
upvar ::punk::config::running running_config
if {[string length [dict get $running_config color_stdout_repl]] && [interp eval code punk::console::colour]} {
lappend outstack [interp eval code [list ::shellfilter::stack add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout_repl]]]]
}
lappend outstack [interp eval code [list ::shellfilter::stack add stdout tee_to_var -settings {-varname ::punk::repl::codethread::output_stdout}]]
if {[string length [dict get $running_config color_stderr_repl]] && [interp eval code punk::console::colour]} {
lappend errstack [interp eval code [list ::shellfilter::stack add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr_repl]]]]
# #lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour cyan]]
}
lappend errstack [interp eval code [list ::shellfilter::stack add stderr tee_to_var -settings {-varname ::punk::repl::codethread::output_stderr}]]
#an experiment
#set errhandle [shellfilter::stack::item_tophandle stderr]
#interp transfer "" $errhandle code
set status [catch {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
#interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code [list ::codeinterp::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {lappend ::codeinterp::run_command_cache $::codeinterp::clonescript}
interp eval code {
#lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
if {[string first ":::" $::punk::ns::ns_current] >= 0} {
#support for browsing 'odd' (inadvisable) namespaces
#don't use 'namespace exists' - will conflate ::test::x with ::test:::x
#if {$::punk::ns::ns_current in [namespace children [punk::ns::nsprefix $::punk::ns::ns_current]} {
#}
package require punk::ns
punk::ns::nseval_ifexists $::punk::ns::ns_current $::codeinterp::clonescript
} else {
if {![namespace exists $::punk::ns::ns_current]} {
namespace eval $::punk::ns::ns_current {
puts stderr "Created namespace: $::punk::ns::ns_current"
}
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
}
} result]
#temp test for subshell experimentation
#if {$status == 1} {
# puts stderr "--codethread::runscript error--------\n$::errorInfo"
#}
flush stdout
flush stderr
#interp transfer code $errhandle ""
#flush $errhandle
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end]
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code [list set ::punk::repl::codethread::output_stdout]]] end]
set lastoutpart [interp eval code {string range $::punk::repl::codethread::output_stdout end-100 end}]
#note we could be in a *large* ansi segment such as sixel data
#review - why do we need to ansistrip?
set lastoutchar [string index [punk::ansi::ansistrip $lastoutpart] end]
#set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end]
set lasterrpart [interp eval code {string range $::punk::repl::codethread::output_stderr end-100 end}]
set lasterrchar [string index [punk::ansi::ansistrip $lasterrpart] end]
#puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]"
set tid [thread::id]
tsv::set codethread_$tid info [list lastoutchar $lastoutchar lasterrchar $lasterrchar]
tsv::set codethread_$tid status $status
tsv::set codethread_$tid result $result
tsv::set codethread_$tid errorcode $::errorCode
#only remove from shellfilter::stack the items we added to stack in this function
foreach s [lreverse $outstack] {
interp eval code [list ::shellfilter::stack remove stdout $s]
}
foreach s [lreverse $errstack] {
interp eval code [list ::shellfilter::stack remove stderr $s]
}
thread::cond notify $replthread_cond
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread::lib {
tcl::namespace::export *
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::repl::codethread::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::repl::codethread::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
tcl::namespace::eval punk::repl::codethread::system {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::repl::codethread [tcl::namespace::eval punk::repl::codethread {
variable pkg punk::repl::codethread
variable version
set version 0.1.1
}]
return
#*** !doctools
#[manpage_end]

382
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm vendored

@ -27,6 +27,11 @@
# #
# path/repo functions # path/repo functions
# #
#REVIEW punk::repo required early by punk boot script to find projectdir
#todo - split off basic find_project chain of functions to a smaller package and import as necessary here
#Then we can reduce early dependencies in punk boot
if {$::tcl_platform(platform) eq "windows"} { if {$::tcl_platform(platform) eq "windows"} {
package require punk::winpath package require punk::winpath
} else { } else {
@ -57,6 +62,94 @@ package require punk::mix::util ;#do_in_path
# -- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- ---
namespace eval punk::repo { namespace eval punk::repo {
variable PUNKARGS
variable PUNKARGS_aliases
variable cached_command_paths
set cached_command_paths [dict create]
#anticipating possible removal of buggy caching from auto_execok
#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.
proc Cached_auto_execok {name} {
return [auto_execok $name]
#variable cached_command_paths
#if {[dict exists $cached_command_paths $name]} {
# return [dict get $cached_command_paths $name]
#}
#set resolved [auto_execok $name]
#dict set cached_command_paths $name $resolved
#return $resolved
}
proc get_fossil_usage {} {
set allcmds [runout -n fossil help -a]
set mainhelp [runout -n fossil help]
set maincommands [list]
foreach ln [split $mainhelp \n] {
set ln [string trim $ln]
if {$ln eq "" || [regexp {^[A-Z]+} $ln]} {
continue
}
lappend maincommands {*}$ln
}
set othercmds [punk::lib::ldiff $allcmds $maincommands]
set result "@leaders -min 0\n"
append result [tstr -return string {
subcommand -type string -choicecolumns 8 -choicegroups {
"frequently used commands" {${$maincommands}}
"" {${$othercmds}}
}
}]
return $result
}
#lappend PUNKARGS [list {
# @dynamic
# @id -id ::punk::repo::fossil_proxy
# @cmd -name fossil -help "fossil executable
# "
# @argdisplay -header "fossil help" -body {${[runout -n fossil help]}}
# } ""]
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::repo::fossil_proxy
@cmd -name fossil -help "fossil executable"
${[punk::repo::get_fossil_usage]}
} ]
#experiment
lappend PUNKARGS [list {
@dynamic
@id -id "::punk::repo::fossil_proxy diff"
@cmd -name "fossil diff" -help "fossil diff"
@argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}}
} ""]
lappend PUNKARGS [list {
#todo - remove this comment - testing dynamic directive
@dynamic
@id -id "::punk::repo::fossil_proxy add"
@cmd -name "fossil add" -help "fossil add
"
@argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
} ""]
#TODO
#lappend PUNKARGS [list {
# @dynamic
# @id -glob 1 -id "::punk::repo::fossil_proxy *" -aliases {fs}
# @cmd -name "fossil add" -help "fossil add
# "
# @argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
# } ""]
lappend PUNKARGS_aliases {"::fossil" "::punk::repo::fossil_proxy"}
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
@ -76,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"]
@ -123,24 +216,44 @@ 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 [auto_execok fossil] set fossil_prog [Cached_auto_execok fossil]
if {$fossil_prog ne ""} { if {$fossil_prog ne ""} {
{*}$fossil_prog {*}$args {*}$fossil_prog {*}$args
} else { } else {
puts stderr "fossil command not found. Please install fossil" puts stderr "fossil command not found. Please install fossil"
} }
} }
interp alias "" fossil "" punk::repo::fossil_proxy
# ---
# Calling auto_execok on an external tool can be too slow to do during package load (e.g could be 150ms)
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
catch { #catch {
if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
} # }
#}
# ---
# ----------
#
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy
#only necessary on unix?
#Windows filesystem case insensitive so any non-lowercase fossil version goes out to get an ::auto_execs entry anyway
proc establish_FOSSIL {args} {
#review
if {![info exists ::auto_execs(FOSSIL)]} {
set ::auto_execs(FOSSIL) [auto_execok fossil] ;#may fail in safe interp
}
interp alias "" FOSSIL "" ;#delete establishment alias
FOSSIL {*}$args
} }
# ----------
proc askuser {question} { proc askuser {question} {
if {![catch {package require punk::lib}]} { if {![catch {package require punk::lib}]} {
@ -216,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 {
@ -234,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"} {
@ -266,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
@ -303,9 +476,17 @@ 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
@ -329,6 +510,11 @@ namespace eval punk::repo {
#does a dual git/fossil repo make sense if both are committing?? #does a dual git/fossil repo make sense if both are committing??
# see: https://fossil-scm.org/home/doc/trunk/www/inout.wiki for bidirectional sync info # see: https://fossil-scm.org/home/doc/trunk/www/inout.wiki for bidirectional sync info
proc workingdir_state {{abspath {}} args} { proc workingdir_state {{abspath {}} args} {
#we should try to minimize executable calls
#an extra git/fossil executable call required for tags
#git seems to require more executable calls
set defaults [list\ set defaults [list\
-repotypes [list fossil git]\ -repotypes [list fossil git]\
-repopaths ""\ -repopaths ""\
@ -343,7 +529,7 @@ namespace eval punk::repo {
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
set opt_repopaths [dict get $opts -repopaths] set opt_repopaths [dict get $opts -repopaths]
if {"$opt_repopaths" ne ""} { if {"$opt_repopaths" ne ""} {
if {([llength $opt_repopaths] % 2 != 0) || ![dict exists $opt_repopaths closest]} { if {([llength $opt_repopaths] % 2) || ![dict exists $opt_repopaths closest]} {
error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos" error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos"
} }
set repopaths $opt_repopaths set repopaths $opt_repopaths
@ -370,7 +556,16 @@ namespace eval punk::repo {
} }
if {$repodir eq ""} { if {$repodir eq ""} {
error "workingdir_state error: No repository found at or above path '$abspath'" puts stderr "workingdir_state error: No repository found at or above path '$abspath'"
puts stderr "args: $args"
dict set resultdict revision {}
dict set resultdict revision_iso8601 {}
dict set resultdict paths {}
dict set resultdict ahead ""
dict set resultdict behind ""
dict set resultdict error {reason "no_repo_found"}
dict set resultdict repotype none
return $resultdict
} }
set subpath [punk::path::relative $repodir $abspath] set subpath [punk::path::relative $repodir $abspath]
if {$subpath eq "."} { if {$subpath eq "."} {
@ -382,6 +577,8 @@ namespace eval punk::repo {
set revision "" set revision ""
set revision_iso8601 "" set revision_iso8601 ""
set pathdict [dict create] set pathdict [dict create]
set branch ""
set tags ""
if {![llength $repotypes_to_query]} { if {![llength $repotypes_to_query]} {
error "No tracking information available for project at $repodir with the chosen repotypes '$opt_repotypes'. Ensure project workingdir is a fossil (or git) checkout" error "No tracking information available for project at $repodir with the chosen repotypes '$opt_repotypes'. Ensure project workingdir is a fossil (or git) checkout"
@ -394,7 +591,7 @@ namespace eval punk::repo {
#For this reason we will store 'unchanged' records for both git and fossil so that the combined dict should represent all files in the revision #For this reason we will store 'unchanged' records for both git and fossil so that the combined dict should represent all files in the revision
if {$rt eq "fossil"} { if {$rt eq "fossil"} {
dict set resultdict repotype fossil dict set resultdict repotype fossil
set fossil_cmd [auto_execok fossil] set fossil_cmd [Cached_auto_execok fossil]
if {$fossil_cmd eq ""} { if {$fossil_cmd eq ""} {
error "workingdir_state error: fossil executable doesn't seem to be available" error "workingdir_state error: fossil executable doesn't seem to be available"
} }
@ -411,6 +608,38 @@ namespace eval punk::repo {
} }
set revision_iso8601 "${revision_ymd}T${revision_hms}${revision_tz}" set revision_iso8601 "${revision_ymd}T${revision_hms}${revision_tz}"
#REVIEW! what are the semantic difference between tags in fossil v git?
#fossil has tagtypes such as propagated and singleton(onetime)
#if we get all tag info for the revision - we can get the current branch (branch=somename tag) at the same time
#by retrieving with --raw - we have to process some prefixes such as sym- but probably best not done here
#we will return all tags that apply to the current revision and let the caller decide the meanings
if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd tag ls --raw $revision]} cmdresult]} {
set branchinfo [lindex [grep {branch=*} $cmdresult] 0] ;#first line match - should only be one
set branch [lindex [split $branchinfo =] 1]
set tags [list]
foreach ln [split $cmdresult \n] {
if {[string trim $ln] eq ""} {
continue
}
lappend tags [string trim $ln]
}
}
#set tags_info [lindex [grep {tags:*} $fossilstate 0] ;#first line match - should only be one
#we get lines like:
#tags: trunk, main
#tags: trunk
#set rawtags [lrange $tags_info 1 end] ;#REVIEW
#set tags [list]
#foreach t $rawtags {
# lappend tags [string trimright $t ,]
#}
#if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$fossil_cmd branch current]} cmdresult]} {
# set branch $cmdresult ;#command result doesn't include newline etc
#}
dict set resultdict ahead "" dict set resultdict ahead ""
dict set resultdict behind "" dict set resultdict behind ""
@ -442,7 +671,7 @@ namespace eval punk::repo {
set path [string trim [string range $ln [string length "MISSING "] end]] set path [string trim [string range $ln [string length "MISSING "] end]]
dict set pathdict $path "missing" dict set pathdict $path "missing"
} }
"EXTRA * " { "EXTRA *" {
#fossil will explicitly list files in a new folder - as opposed to git which shows just the folder #fossil will explicitly list files in a new folder - as opposed to git which shows just the folder
set path [string trim [string range $ln [string length "EXTRA "] end]] set path [string trim [string range $ln [string length "EXTRA "] end]]
dict set pathdict $path "extra" dict set pathdict $path "extra"
@ -461,9 +690,10 @@ namespace eval punk::repo {
break break
} elseif {$rt eq "git"} { } elseif {$rt eq "git"} {
dict set resultdict repotype git dict set resultdict repotype git
set git_cmd [auto_execok git] set git_cmd [Cached_auto_execok git]
# -uno = suppress ? lines. # -uno = suppress ? lines.
# -b = show ranch and tracking info # -b = show ranch and tracking info
#our basic parsing/grepping assumes --porcelain=2
if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd status --porcelain=2 -b -- $abspath]} gitstate]} { if {[catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd status --porcelain=2 -b -- $abspath]} gitstate]} {
error "workingdir_state error: Unable to retrieve workingdir state using git. Errormsg: $gitstate" error "workingdir_state error: Unable to retrieve workingdir state using git. Errormsg: $gitstate"
} }
@ -473,6 +703,13 @@ namespace eval punk::repo {
puts stderr "workingdir_state: git revision is (initial) - no file state to gather" puts stderr "workingdir_state: git revision is (initial) - no file state to gather"
break break
} }
# line: # branch.head somebranchname
set branch [lindex [grep {# branch.head *} $gitstate] 0 2]
if {![catch {punk::mix::util::do_in_path $repodir [list exec {*}$git_cmd describe --exact-match --tags]} cmdresult]} {
set tags $cmdresult ;#review - we have short tags vs longer.. e.g v0.1a vs v0.1a-184-g856fab4 - which is returned? Also how are multiple separated?
}
#often there will be no tag - so the common case is actually an error "fatal: not ag exactly matchs 'xxxx...'"
# -- --- --- --- --- # -- --- --- --- ---
#could use %ci for ISO8601 data - see git-show manpage, but this will be in timezone of developer's machine - we need it in UTC for comparison to fossil outputs and other devs #could use %ci for ISO8601 data - see git-show manpage, but this will be in timezone of developer's machine - we need it in UTC for comparison to fossil outputs and other devs
@ -574,9 +811,11 @@ namespace eval punk::repo {
puts stderr "workingdir_state - repotype $rt not supported" puts stderr "workingdir_state - repotype $rt not supported"
} }
} }
dict set resultdict revision $revision dict set resultdict branch $branch
dict set resultdict revision_iso8601 $revision_iso8601 dict set resultdict tags $tags
dict set resultdict paths $pathdict dict set resultdict revision $revision
dict set resultdict revision_iso8601 $revision_iso8601
dict set resultdict paths $pathdict
return $resultdict return $resultdict
} }
proc workingdir_state_summary {repostate args} { proc workingdir_state_summary {repostate args} {
@ -584,8 +823,13 @@ namespace eval punk::repo {
error "workingdir_state_summary error repostate doesn't appear to be a repostate dict. (use workingdir_state <path> to create)" error "workingdir_state_summary error repostate doesn't appear to be a repostate dict. (use workingdir_state <path> to create)"
} }
package require overtype package require overtype
#the revision branch and tags are highly relevant to the file state - and workingdir_state currently retrieves them anyway
# - so we'll include them in the defaults
# - when we are including working dir state as part of other output - we could be duplicating branch/tag retrievals
# - todo - flags to stop duplicating effort ??
set defaults [dict create\ set defaults [dict create\
-fields {ahead behind unchanged changed new missing extra}\ -fields {revision branch tags ahead behind unchanged changed new missing extra}\
] ]
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- ---
@ -599,6 +843,8 @@ namespace eval punk::repo {
subpath subpath\ subpath subpath\
revision revision\ revision revision\
revision_iso8601 revision_iso8601\ revision_iso8601 revision_iso8601\
branch branch\
tags tags\
ahead ahead\ ahead ahead\
behind behind\ behind behind\
repotype repotype\ repotype repotype\
@ -623,7 +869,7 @@ namespace eval punk::repo {
lappend col2_values [dict get $summary_dict $f] lappend col2_values [dict get $summary_dict $f]
} }
set title1 "" set title1 ""
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $col1_fields] {string length $v}]] set widest1 [tcl::mathfunc::max {*}[lmap v [list $title1 {*}$col1_fields] {string length $v}]]
set col1 [string repeat " " $widest1] set col1 [string repeat " " $widest1]
set title2 "" set title2 ""
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $col2_values] {string length $v}]] set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $col2_values] {string length $v}]]
@ -636,14 +882,26 @@ namespace eval punk::repo {
set result [string trimright $result \n] set result [string trimright $result \n]
return $result return $result
} }
#todo - describe purpose and possibly rename
proc workingdir_state_summary_dict {repostate} { proc workingdir_state_summary_dict {repostate} {
if {![dict exists $repostate repotype] || ![dict exists $repostate paths]} { if {![dict exists $repostate repotype] || ![dict exists $repostate paths]} {
error "workingdir_state_summary_dict error repostate doesn't appear to be a repostate dict. (use workingdir_state <path> to create)" error "workingdir_state_summary_dict error repostate doesn't appear to be a repostate dict. (use workingdir_state <path> to create)"
} }
set filestates [dict values [dict get $repostate paths]] set filestates [dict values [dict get $repostate paths]]
set path_count_fields [list unchanged changed new missing extra] set path_count_fields [list unchanged changed new missing extra]
set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601 branch tags]
set dresult [dict create] set dresult [dict create]
if {[dict exists $repostate error]} {
foreach f $state_fields {
dict set dresult $f ""
}
foreach f $path_count_fields {
dict set dresult $f ""
}
#todo?
return $dresult
}
foreach f $state_fields { foreach f $state_fields {
dict set dresult $f [dict get $repostate $f] dict set dresult $f [dict get $repostate $f]
} }
@ -655,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
@ -669,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
@ -677,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
@ -685,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
@ -747,12 +1016,12 @@ namespace eval punk::repo {
} }
set closest_fossil [lindex [dict get $root_dict fossil] 0] set closest_fossil [lindex [dict get $root_dict fossil] 0]
set closest_fossil_len [llength [file split $closest_fossil]] set closest_fossil_len [llength [file split $closest_fossil]]
set closest_git [lindex [dict get $root_dict git] 0] set closest_git [lindex [dict get $root_dict git] 0]
set closest_git_len [llength [file split $closest_git]] set closest_git_len [llength [file split $closest_git]]
set closest_candidate [lindex [dict get $root_dict candidate] 0] set closest_candidate [lindex [dict get $root_dict candidate] 0]
set closest_candidate_len [llength [file split $closest_candidate]] 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
@ -822,7 +1091,7 @@ namespace eval punk::repo {
} }
proc fossil_get_repository_file {{path {}}} { proc fossil_get_repository_file {{path {}}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
set fossilcmd [auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
set fossilinfo [::exec {*}$fossilcmd info] set fossilinfo [::exec {*}$fossilcmd info]
@ -907,7 +1176,7 @@ namespace eval punk::repo {
set startdir $opt_parentfolder set startdir $opt_parentfolder
set fossil_prog [auto_execok fossil] set fossil_prog [Cached_auto_execok fossil]
if {$fossil_prog eq ""} { if {$fossil_prog eq ""} {
puts stderr "Fossil not found. Please install fossil" puts stderr "Fossil not found. Please install fossil"
return return
@ -1146,27 +1415,27 @@ namespace eval punk::repo {
#------------------------------------ #------------------------------------
#limit to exec so full punk shell not required in scripts #limit to exec so full punk shell not required in scripts
proc git_revision {{path {}}} { proc git_revision {{path ""}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
# ::kettle::path::revision.git # ::kettle::path::revision.git
do_in_path $path { do_in_path $path {
try { try {
#git describe will error with 'No names found' if repo has no tags #git describe will error with 'No names found' if repo has no tags
#set v [::exec {*}[auto_execok git] describe] #set v [::exec {*}[auto_execok git] describe]
set v [::exec {*}[auto_execok git] rev-parse HEAD] ;# consider 'git rev-parse --short HEAD' set v [::exec {*}[Cached_auto_execok git] rev-parse HEAD] ;# consider 'git rev-parse --short HEAD'
} on error {e o} { } on error {e o} {
set v [lindex [split [dict get $o -errorinfo] \n] 0] set v [lindex [split [dict get $o -errorinfo] \n] 0]
} }
} }
return [string trim $v] return [string trim $v]
} }
proc git_remote {{path {{}}}} { proc git_remote {{path ""}} {
if {$path eq {}} { set path [pwd] } if {$path eq {}} { set path [pwd] }
do_in_path $path { do_in_path $path {
try { try {
#git describe will error with 'No names found' if repo has no tags #git describe will error with 'No names found' if repo has no tags
#set v [::exec {*}[auto_execok git] describe] #set v [::exec {*}[auto_execok git] describe]
set v [::exec {*}[auto_execok git] remote -v] ;# consider 'git rev-parse --short HEAD' set v [::exec {*}[Cached_auto_execok git] remote -v] ;# consider 'git rev-parse --short HEAD'
} on error {e o} { } on error {e o} {
set v [lindex [split [dict get $o -errorinfo] \n] 0] set v [lindex [split [dict get $o -errorinfo] \n] 0]
} }
@ -1177,7 +1446,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 [auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
set info [::exec {*}$fossilcmd info] set info [::exec {*}$fossilcmd info]
@ -1191,7 +1460,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 [auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {[llength $fossilcmd]} { if {[llength $fossilcmd]} {
do_in_path $path { do_in_path $path {
set info [::exec {*}$fossilcmd remote ls] set info [::exec {*}$fossilcmd remote ls]
@ -1257,7 +1526,7 @@ namespace eval punk::repo {
set original_cwd [pwd] set original_cwd [pwd]
#attempt2 - let fossil do it for us - hopefully based on current folder #attempt2 - let fossil do it for us - hopefully based on current folder
if {$path eq {}} {set path [pwd]} if {$path eq {}} {set path [pwd]}
set fossilcmd [auto_execok fossil] set fossilcmd [Cached_auto_execok fossil]
if {![llength $fossilcmd]} { if {![llength $fossilcmd]} {
set fossil_ok 0 set fossil_ok 0
} else { } else {
@ -1478,6 +1747,8 @@ namespace eval punk::repo {
} }
} }
interp alias "" fossil "" punk::repo::fossil_proxy
interp alias "" FOSSIL "" punk::repo::establish_FOSSIL
interp alias {} is_fossil {} ::punk::repo::is_fossil interp alias {} is_fossil {} ::punk::repo::is_fossil
interp alias {} is_fossil_root {} ::punk::repo::is_fossil_root interp alias {} is_fossil_root {} ::punk::repo::is_fossil_root
@ -1510,6 +1781,15 @@ namespace eval punk::repo::lib {
} }
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
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::repo
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready ## Ready
package provide punk::repo [namespace eval punk::repo { package provide punk::repo [namespace eval punk::repo {

11
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/tdl-0.1.0.tm vendored

@ -31,15 +31,19 @@ namespace eval punk::tdl {
server -name trillion -os windows server -name trillion -os windows
server -name vmhost1 -os FreeBSD { server -name vmhost1 -os FreeBSD {
guest -name bsd1 -vmmanager iocage guest -name bsd1 -vmmanager bastille
guest -name p1 -vmmanager bhyve guest -name p1 -vmmanager bhyve
} }
} }
proc prettyparse {script} { proc prettyparse {script {safe 1}} {
set i [interp create -safe] if {$safe} {
set i [interp create -safe]
} else {
set i [interp create]
}
try { try {
# $i eval {unset {*}[info vars]} # $i eval {unset {*}[info vars]}
# foreach command [$i eval {info commands}] {$i hide $command} # foreach command [$i eval {info commands}] {$i hide $command}
@ -65,6 +69,7 @@ namespace eval punk::tdl {
interp delete $i interp delete $i
} }
} }
proc prettyprint {data {level 0}} { proc prettyprint {data {level 0}} {
set ind [string repeat " " $level] set ind [string repeat " " $level]
incr level incr level

605
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/trie-0.1.0.tm vendored

@ -0,0 +1,605 @@
# -*- 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.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) CMcC 2010
#
# @@ Meta Begin
# Application punk::trie 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::trie 0 0.1.0]
#[copyright "2010"]
#[titledesc {punk::trie API}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk::trie}] [comment {-- Description at end of page heading --}]
#[require punk::trie]
#[keywords module datastructure trie]
#[description] tcl trie implementation courtesy of CmcC (tcl wiki)
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::trie
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::trie
#[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::trie::class {
# #*** !doctools
# #[subsection {Namespace punk::trie::class}]
# #[para] class definitions
# #if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# #}
# #}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::trie {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
proc Dolog {lvl txt} {
#return "$lvl -- $txt"
#logger calls this in such a way that a straight uplevel can get us the vars/commands in messages substituted
set msg "[clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%S"] punk::trie '[uplevel [list subst $txt]]'"
puts stderr $msg
}
package require logger
logger::initNamespace ::punk::trie
foreach lvl [logger::levels] {
interp alias {} ::punk::trie::Log_$lvl {} ::punk::trie::Dolog $lvl
log::logproc $lvl ::punk::trie::Log_$lvl
}
#namespace path ::punk::trie::log
#*** !doctools
#[subsection {Namespace punk::trie}]
#[para] Core API functions for punk::trie
if {[tcl::info::commands [tcl::namespace::current]::trieclass] eq ""} {
#*** !doctools
#[list_begin enumerated]
oo::class create [tcl::namespace::current]::trieclass {
#*** !doctools
#[enum] CLASS [class trieclass]
#[list_begin definitions]
variable trie id
method matches {t what} {
#*** !doctools
#[call class::trieclass [method matches] [arg t] [arg what]]
#[para] search for longest prefix, return matching prefix, element and suffix
set matches {}
set wlen [string length $what]
foreach k [lsort -decreasing -dictionary [dict keys $t]] {
set klen [string length $k]
set match ""
for {set i 0} {$i < $klen
&& $i < $wlen
&& [string index $k $i] eq [string index $what $i]
} {incr i} {
append match [string index $k $i]
}
if {$match ne ""} {
lappend matches $match $k
}
}
#Debug.trie {matches: $what -> $matches}
::punk::trie::log::debug {matches: $what -> $matches}
if {[dict size $matches]} {
# find the longest matching prefix
set match [lindex [lsort -dictionary [dict keys $matches]] end]
set mel [dict get $matches $match]
set suffix [string range $what [string length $match] end]
return [list $match $mel $suffix]
} else {
return {} ;# no matches
}
}
# return next unique id if there's no proffered value
method id {value} {
if {$value} {
return $value
} else {
return [incr id]
}
}
# insert an element with a given optional value into trie
# along path given by $args (no need to specify)
method insert {what {value 0} args} {
if {[llength $args]} {
set t [dict get $trie {*}$args]
} else {
set t $trie
}
if {[dict exists $t $what]} {
#Debug.trie {$what is an exact match on path ($args $what)}
::punk::trie::log::debug {$what is an exact match on path ($args $what)}
if {[catch {dict size [dict get $trie {*}$args $what]} size]} {
# the match is a leaf - we're done
} else {
# the match is a dict - we have to add a null
dict set trie {*}$args $what "" [my id $value]
}
return ;# exact match - no change
}
# search for longest prefix
set match [my matches $t $what]
if {![llength $match]} {
;# no matching prefix - new element
#Debug.trie {no matching prefix of '$what' in $t - add it on path ($args $what)}
::punk::trie::log::debug {no matching prefix of '$what' in $t add it on path ($args $what)}
dict set trie {*}$args $what [my id $value]
return
}
lassign $match match mel suffix ;# prefix, element of match, suffix
if {$match ne $mel} {
# the matching element shares a prefix, but has a variant suffix
# it must be split
#Debug.trie {splitting '$mel' along '$match'}
::punk::trie::log::debug {splitting '$mel' along '$match'}
set melC [dict get $t $mel]
dict unset trie {*}$args $mel
dict set trie {*}$args $match [string range $mel [string length $match] end] $melC
}
if {[catch {dict size [dict get $trie {*}$args $match]} size]} {
# the match is a leaf - must be split
if {$match eq $mel} {
# the matching element shares a prefix, but has a variant suffix
# it must be split
#Debug.trie {splitting '$mel' along '$match'}
::punk::trie::log::debug {splitting '$mel' along '$match'}
set melC [dict get $t $mel]
dict unset trie {*}$args $mel
dict set trie {*}$args $match "" $melC
}
#Debug.trie {'$mel' is the longest prefix '$match' but was a leaf - insert '$suffix'}
::punk::trie::log::debug {'$mel' is the longest prefix '$match' but was a leaf - insert '$suffix'}
set melid [dict get $t $mel]
dict set trie {*}$args $match $suffix [my id $value]
} else {
# it's a dict - keep searching
#Debug.trie {'$mel' is the longest prefix '$match' and is a dict - search for '$suffix' on path ($args $match)}
::punk::trie::log::debug {'$mel' is the longest prefix '$match' and is a dict - search for '$suffix' on path ($args $match)}
my insert $suffix $value {*}$args $match
}
return
}
# find a path matching an element $what
# if the element's not found, return the nearest path
method find_path {what args} {
if {[llength $args]} {
set t [dict get $trie {*}$args]
} else {
set t $trie
}
if {[dict exists $t $what]} {
#Debug.trie {$what is an exact match on path ($args $what)}
return [list {*}$args $what] ;# exact match - no change
}
# search for longest prefix
set match [my matches $t $what]
if {![llength $match]} {
return $args
}
lassign $match match mel suffix ;# prefix, element of match, suffix
if {$match ne $mel} {
# the matching element shares a prefix, but has a variant suffix
# no match
return $args
}
if {[catch {dict size [dict get $trie {*}$args $match]} size] || $size == 0} {
# got to a non-matching leaf - no match
return $args
} else {
# it's a dict - keep searching
#Debug.trie {'$mel' is the longest prefix '$match' and is a dict - search for '$suffix' on path ($args $match)}
return [my find_path $suffix {*}$args $match]
}
}
# given a trie, which may have been modified by deletion,
# optimize it by removing empty nodes and coalescing singleton nodes
method optimize {args} {
if {[llength $args]} {
set t [dict get $trie {*}$args]
} else {
set t $trie
}
if {[catch {dict size $t} size]} {
#Debug.trie {optimize leaf '$t' along '$args'}
::punk::trie::log::debug {optimize leaf '$t' along '$args'}
# leaf - leave it
} else {
switch -- $size {
0 {
#Debug.trie {optimize empty dict ($t) along '$args'}
::punk::trie::log::debug {optimize empty dict ($t) along '$args'}
if {[llength $args]} {
dict unset trie {*}$args
}
}
1 {
#Debug.trie {optimize singleton dict ($t) along '$args'}
::punk::trie::log::debug {optimize singleton dict ($t) along '$args'}
lassign $t k v
if {[llength $args]} {
dict unset trie {*}$args
}
append args $k
if {[llength $v]} {
dict set trie {*}$args $v
}
my optimize {*}$args
}
default {
#Debug.trie {optimize dict ($t) along '$args'}
::punk::trie::log::debug {optimize dict ($t) along '$args'}
dict for {k v} $t {
my optimize {*}$args $k
}
}
}
}
}
# delete element $what from trie
method delete {what} {
set path [my find_path $what]
if {[join $path ""] eq $what} {
#Debug.trie {del '$what' along ($path) was [dict get $trie {*}$path]}
if {[catch {dict size [dict get $trie {*}$path]} size]} {
# got to a matching leaf - delete it
dict unset trie {*}$path
set path [lrange $path 0 end-1]
} else {
dict unset trie {*}$path ""
}
my optimize ;# remove empty and singleton elements
} else {
# nothing to delete, guess we're done
}
}
# find the value of element $what in trie,
# error if not found
method find_or_error {what} {
set path [my find_path $what]
if {[join $path ""] eq $what} {
if {[catch {dict size [dict get $trie {*}$path]} size]} {
# got to a matching leaf - done
return [dict get $trie {*}$path]
} else {
#JMN - what could be an exact match for a path, but not be in the trie itself
if {[dict exists $trie {*}$path ""]} {
return [dict get $trie {*}$path ""]
} else {
::punk::trie::log::debug {'$what' matches a path but is not a leaf}
error "'$what' not found"
}
}
} else {
error "'$what' not found"
}
}
#JMN - renamed original find to find_or_error
#prefer not to catch on result - but test for -1
method find {what} {
set path [my find_path $what]
if {[join $path ""] eq $what} {
#presumably we use catch and dict size to avoid llength shimmering large inner dicts to list rep
if {[catch {dict size [dict get $trie {*}$path]} size]} {
# got to a matching leaf - done
return [dict get $trie {*}$path]
} else {
#JMN - what could be an exact match for a path, but not be in the trie itself
if {[dict exists $trie {*}$path ""]} {
return [dict get $trie {*}$path ""]
} else {
::punk::trie::log::debug {'$what' matches a path but is not a leaf}
return -1
}
}
} else {
return -1
}
}
# dump the trie as a string
method dump {} {
return $trie
}
# return a string rep of the trie sorted in dict order
method order {{t {}}} {
if {![llength $t]} {
set t $trie
} elseif {[llength $t] == 1} {
return $t
}
set acc {}
foreach key [lsort -dictionary [dict keys $t]] {
lappend acc $key [my order [dict get $t $key]]
}
return $acc
}
# return the trie as a dict of names with values
method flatten {{t {}} {prefix ""}} {
if {![llength $t]} {
set t $trie
} elseif {[llength $t] == 1} {
return [list $prefix $t]
}
set acc {}
dict for {key val} $t {
lappend acc {*}[my flatten $val $prefix$key]
}
return $acc
}
#shortest possible string to identify an element in the trie using the same principle as tcl::prefix::match
#ie if a stored word is a prefix of any other words - it must be fully specified to identify itself.
#JMN - REVIEW - better algorithms?
#caller having retained all members can avoid flatten call
#by selecting a single 'which' known not to be in the trie (or empty string) - all idents can be returned.
#when all 'which' members are in the tree - scanning stops when they're all found
# - and a dict containing result and scanned keys is returned
# - result contains a dict with keys for each which member
# - scanned contains a dict of all words longer than our shortest which - (and a subset of words the same length)
method shortest_idents {which {allmembers {}}} {
set t $trie
if {![llength $allmembers]} {
set members [dict keys [my flatten]]
} else {
set members $allmembers
}
set len_members [lmap m $members {list [string length $m] $m}]
set longestfirst [lsort -index 0 -integer -decreasing $len_members]
set longestfirst [lmap v $longestfirst {lindex $v 1}]
set taken [dict create]
set scanned [dict create]
set result [dict create] ;#words in our which list - if found
foreach w $longestfirst {
set path [my find_path $w]
if {[dict exists $taken $w]} {
#whole word - no unique prefix
dict set scanned $w $w
if {$w in $which} {
#puts stderr "$w -> $w"
dict set result $w $w
if {[dict size $result] == [llength $which]} {
return [dict create result $result scanned $scanned]
}
}
continue
}
set acc ""
foreach p [lrange $path 0 end-1] {
dict set taken [append acc $p] 1 ;#no need to test first - just set even though may already be present
}
append acc [string index [lindex $path end] 0]
dict set scanned $w $acc ;#sorted by length - so no other can have this prefix - and no longer necessary
if {$w in $which} {
#puts stderr "$w -> $acc"
dict set result $w $acc
if {[dict size $result] == [llength $which]} {
return [dict create result $result scanned $scanned]
}
}
}
return [dict create result $result scanned $scanned]
}
# overwrite the trie
method set {t} {
set trie $t
}
constructor {args} {
set trie {}
set id 0
foreach a $args {
my insert $a
}
}
#*** !doctools
#[list_end] [comment {--- end definitions ---}]
}
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
set testlist [list blah x black blacken]
proc test1 {} {
#JMN
#test that find_or_error of a path that isn't stored as a value returns an appropriate error
#(used to report couldn't find dict key "")
set t [punk::trie::trieclass new blah x black blacken]
if {[catch {$t find_or_error bla} errM]} {
puts stderr "should be error indicating 'bla' not found"
puts stderr "err during $t find bla\n$errM"
}
return $t
}
# 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}]
# }
}
#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"
#}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::trie::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::trie::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::trie::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::trie::system {
#*** !doctools
#[subsection {Namespace punk::trie::system}]
#[para] Internal functions that are not part of the API
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::trie [tcl::namespace::eval punk::trie {
variable pkg punk::trie
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

237
src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/punk/unixywindows-0.1.0.tm vendored

@ -0,0 +1,237 @@
# -*- 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) 2023
#
# @@ Meta Begin
# Application punk::unixywindows 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
#for illegalname_test
package require punk::winpath
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::unixywindows {
#'cached' name to make obvious it could be out of date - and to distinguish from unixyroot arg
variable cachedunixyroot ""
#-----------------
#e.g something like c:/Users/geek/scoop/apps/msys2/current c:/msys2
proc get_unixyroot {} {
variable cachedunixyroot
if {![string length $cachedunixyroot]} {
if {![catch {
set result [exec cygpath -m /] ;# -m gives result with forward-slashes - which is ok for windows paths in a Tcl context.
set cachedunixyroot [punk::objclone $result]
file pathtype $cachedunixyroot; #this call causes the int-rep to be path
#set ::punk::last_run_display [list] ;#hack - review shouldn't really be necessary.. but because we call winpath from ./ - the repl looks for last_run_display
} errM]} {
} else {
puts stderr "Warning: Failed to determine base for unix-like paths - using default of c:/msys2"
file pathtype [set cachedunixyroot [punk::objclone "c:/msys2"]]
}
}
#will have been shimmered from string to 'path' internal rep by 'file pathtype' call
#let's return a different copy as it's so easy to lose path-rep
set copy [punk::objclone $cachedunixyroot]
return $copy
}
proc refresh_unixyroot {} {
variable cachedunixyroot
set result [exec cygpath -m /] ;# -m gives result with forward-slashes - which is ok for windows paths in a Tcl context.
set cachedunixyroot [punk::objclone $result]
file pathtype $cachedunixyroot; #this call causes the int-rep to be path
set copy [punk::objclone $cachedunixyroot]
return $copy
}
proc set_unixyroot {windows_path} {
variable cachedunixyroot
file pathtype $windows_path
set cachedunixyroot [punk::objclone $windows_path]
#return the original - but probably int-rep will have shimmered to path even if started out as string
#- that's probably what we want. Caller can use as string or path without affecting cachedunixyroot
return $windows_path
}
proc windir {path} {
if {$path eq "~"} {
#as the tilde hasn't been normalized.. we can't assume we're running on the actual platform
return ~/..
}
return [file dirname [towinpath $path]]
}
#REVIEW high-coupling
proc cdwin {path} {
set path [towinpath $path]
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
if {[llength [info commands ::punk::console::titleset]]} {
::punk::console::titleset $path
}
}
cd $path
}
proc cdwindir {path} {
set path [towinpath $path]
if {[package provide punk::repl::codethread] ne "" && [punk::repl::codethread::is_running]} {
if {[llength [info commands ::punk::console::titleset]]} {
::punk::console::titleset $path
}
}
cd [file dirname $path]
}
#NOTE - this is an expensive operation - avoid where possible.
#review - is this intended to be useful/callable on non-windows platforms?
#it should in theory be useable from another platform that wants to create a path for use on windows.
#In this case - we shouldn't examine what volumes exist (assume A: .. Z: are valid)
#review zipfs:// other uri schemes?
proc towinpath {unixypath {unixyroot ""}} {
#NOTE: tcl file exists gives different answers on windows for paths like /c depending on cwd (presumably based on file pathtype of volumerelative)
#(Tcl is also somewhat broken as at 2023 as far as volume relative paths - process can get out of sync with tcl if cd to a vol relative path is used)
#This is add odds with attempting to navigate on a windows system which has cygwin, wsl etc... It also makes it difficult for functions intended to operate independent of CWD.
#e.g there is potential confusion when there is a c folder on c: drive (c:/c)
#I will attempt to provide a coherent operation for winpath ./ ../ etc , but it may disallow for example; change to /something or /x where these don't match a driveletter or /mnt
#whereas tcl may allow cd to /something if a something folder happens to exist on the current volume based on cwd.
#I think it's preferable to require an explicit driveletter /x or /mnt when using unix-like paths on windows - but practical considerations may prove me wrong..
#It's possible that this function should also ignore the current set of driveletters - and operate completely independent of whether a path actually exists
#This makes it hard to use things like 'file normalize' - which also looks at things like current volume.
#
#Note for example the results of 'which' grep on windows can produce a path like /c/Users/somewhere/bin/grep
#which tcl's file normalize may change to C:/c/Users or X:/c/Users - based on current volumen. Given that C:/c might exist - this can be problematic in a couple of ways.
#The mixing of unix-like and windows commands on the same machine is a large part of the problem.. but this mix is now common
#
#convert /c/etc to C:/etc
set re_slash_x_slash {^/([[:alpha:]]){1}/.*}
set re_slash_else {^/([[:alpha:]]*)(.*)}
set volumes [file volumes]
#exclude things like //zipfs:/ ??
set driveletters [list]
foreach v $volumes {
if {[regexp {^([[:alpha:]]){1}:/$} $v _ letter]} {
lappend driveletters $letter
}
}
#puts stderr "->$driveletters"
set path [punk::objclone $unixypath] ;#take another copy that we can deliberatley shimmer to path and know is separate to the supplied argument
set supplied_pathtype [file pathtype $path] ;#we don't care about the pathtype so much as the act of making this call shimmers to a path internal-rep
#copy of var that we can treat as a string without affecting path rep
#Note that some but not all read operations will lose path rep e.g 'string length $x' will lose any path-rep $x had, (but 'string equal $x something' won't)
#Todo - make int-rep tests to check stability of these behaviours across Tcl versions!
set strcopy_path [punk::objclone $path]
set str_newpath ""
set have_pathobj 0
if {[regexp $re_slash_x_slash $strcopy_path _ letter]} {
#upper case appears to be windows canonical form
set str_newpath [string toupper $letter]:/[string range $strcopy_path 3 end]
} elseif {[regexp {^/mnt/([[:alpha:]]){1}/.*} [string tolower $strcopy_path] _ letter]} {
set str_newpath [string toupper $letter]:/[string range $strcopy_path 7 end]
} elseif {[regexp {^/mnt/([[:alpha:]]){1}$} [string tolower $strcopy_path] _ letter]} {
set str_newpath [string toupper $letter]:/
} elseif {[regexp $re_slash_else $strcopy_path _ firstpart remainder]} {
#could be for example /c or /something/users
if {[string length $firstpart] == 1} {
set letter $firstpart
set str_newpath [string toupper $letter]:/
} else {
#according to regex we have a single leading slash
set str_tail [string range $strcopy_path 1 end]
if {$unixyroot eq ""} {
set unixyroot [get_unixyroot]
} else {
file pathtype $unixyroot; #side-effect generates int-rep of type path )
}
set pathobj [file join $unixyroot $str_tail]
file pathtype $pathobj
set have_pathobj 1
}
}
if {!$have_pathobj} {
if {$str_newpath eq ""} {
#dunno - pass through
set pathobj $path
} else {
set pathobj [punk::objclone $str_newpath]
file pathtype $pathobj
}
}
#puts stderr "=> $path"
#things like 'which' seem to return a path minus the .exe - so we'll just test the containing folder
#
#By now file normalize shouldn't do too many shannanigans related to cwd..
#We want it to look at cwd for relative paths..
#but we don't consider things like /c/Users to be relative even on windows where it would normally mean a volume-relative path e.g c:/c/Users if cwd happens to be somewhere on C: at the time.
#if {![file exists [file dirname $path]]} {
# set path [file normalize $path]
# #may still not exist.. that's ok.
#}
#file normalize may change backslashes to forward slashes.. including things like the special \\?\ prefix which is intended to stop windows api from parsing a name
#2023 - this is ok as //?/ also seems to work.. but it is unclear if that is because Tcl is re-converting to backslashes
if {[punk::winpath::illegalname_test $pathobj]} {
set pathobj [punk::winpath::illegalname_fix $pathobj]
}
return $pathobj
}
#----------------------------------------------
#leave the unixywindows related aliases available on all platforms
#interp alias {} cdwin {} punk::unixywindows::cdwin
#interp alias {} cdwindir {} punk::unixywindoes::cdwindir
#interp alias {} towinpath {} punk::unixywindows::towinpath
#interp alias {} windir {} punk::unixywindows::windir
#----------------------------------------------
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::unixywindows [namespace eval punk::unixywindows {
variable version
set version 0.1.0
}]
return

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

Loading…
Cancel
Save