Browse Source

update src/make.tcl and src/bootsupport

master
Julian Noble 1 week ago
parent
commit
c2e6ba71e5
  1. 2
      src/bootsupport/modules/commandstack-0.3.tm
  2. 567
      src/bootsupport/modules/fauxlink-0.1.0.tm
  3. 21
      src/bootsupport/modules/fauxlink-0.1.1.tm
  4. 705
      src/bootsupport/modules/modpod-0.1.0.tm
  5. 697
      src/bootsupport/modules/modpod-0.1.1.tm
  6. 1894
      src/bootsupport/modules/natsort-0.1.1.5.tm
  7. 3
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  8. 65
      src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  9. 5
      src/bootsupport/modules/punk/mix/base-0.1.tm
  10. 18
      src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  11. 6
      src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  12. 8
      src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  13. 26
      src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm
  14. 3
      src/bootsupport/modules/punk/mod-0.1.tm
  15. 21
      src/bootsupport/modules/punk/path-0.1.0.tm
  16. 118
      src/bootsupport/modules/punk/repo-0.1.1.tm
  17. 114
      src/bootsupport/modules/punkcheck-0.1.0.tm
  18. BIN
      src/bootsupport/modules/test/tomlish-1.1.1.tm
  19. BIN
      src/bootsupport/modules/test/tomlish-1.1.3.tm
  20. 7408
      src/bootsupport/modules/textblock-0.1.1.tm
  21. 8520
      src/bootsupport/modules/textblock-0.1.2.tm
  22. 150
      src/bootsupport/modules/tomlish-1.1.2.tm
  23. 1382
      src/bootsupport/modules/tomlish-1.1.3.tm
  24. BIN
      src/bootsupport/modules/zipper-0.11.tm
  25. 70
      src/make.tcl

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

@ -259,7 +259,7 @@ namespace eval commandstack {
variable debug
if $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]"

567
src/bootsupport/modules/fauxlink-0.1.0.tm

@ -1,567 +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 fauxlink 0.1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin fauxlink_module_fauxlink 0 0.1.0]
#[copyright "2024"]
#[titledesc {faux link application shortcuts}] [comment {-- Name section and table of contents description --}]
#[moddesc {fauxlink .fxlnk}] [comment {-- Description at end of page heading --}]
#[require fauxlink]
#[keywords symlink faux fake shortcut toml]
#[description]
#[para] A cross platform shortcut/symlink alternative.
#[para] Unapologetically ugly - but practical in certain circumstances.
#[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as
#[para] archiving and packaging systems.
#[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] where <nominalname> can be empty - then the effective nominal name is the tail of the <encodedtarget>
#[para] The + symbol substitutes for forward-slashes.
#[para] Other chars can be encoded using url-like encoding - (but only up to %7E !)
#[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] 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] file%23A.txt#..+file%23A.txt.fxlnk
#[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fxlnk
#[para] The <nominalname> can be unrelated to the actual target
#[para] e.g datafile.dat#..+file%23A.txt.fxlnk
#[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] 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] 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] Aside from the 2 used for delimiting (+ #)
#[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] Others that require encoding are: * ? \ / | : ; " < >
#[para] The nul character in raw form, when detected, is always mapped away to the empty string - as very few filesystems support it.
#[para] Control characters and other punctuation is optional to encode.
#[para] Generally utf-8 should be used where possible and unicode characters can often be left unencoded on modern systems.
#[para] Where encoding of unicode is desired in the nominalname,encodedtarget,tag or comment portions it can be specified as %UXXXXXXXX
#[para] There must be between 1 and 8 X digits following the %U. Interpretation of chars following %U stops at the first non-hex character.
#[para] This means %Utest would not get any translation as there were no hex digits so it would come out as %Utest
#
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#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.
#Using fauxlink - a link would be:
# "my-program-files#++server+c+Program%20Files.fxlnk"
#If we needed the old-style literal %20 it would become
# "my-program-files#++server+c+Program%2520Files.fxlnk"
#
# The file:// scheme on windows supposedly *does* decode %xx (for use in a browser)
# e.g
# pfiles#file%3a++++localhost+c+Program%2520files
# The browser will work with literal spaces too though - so it could just as well be:
# pfiles#file%3a++++localhost+c+Program%20files
#windows may default to using explorer.exe instead of a browser for file:// urls though
#and explorer doesn't want the literal %20. It probably depends what API the file:// url is to be passed to?
#in a .url shortcut either literal space or %20 will work ie %xx values are decoded
#*** !doctools
#[section Overview]
#[para] overview of fauxlink
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by fauxlink
#[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
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval fauxlink::class {
#*** !doctools
#[subsection {Namespace fauxlink::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 fauxlink {
namespace export {[a-z]*}; # Convention: export all lowercase
#todo - enforce utf-8
#literal unicode chars supported by modern filesystems - leave as is - REVIEW
variable encode_map
variable decode_map
#most filesystems don't allow NULL - map to empty string
#Make sure % is not in encode_map
set encode_map [dict create\
\x00 ""\
{ } %20\
\t %09\
+ %2B\
# %23\
* %2A\
? %3F\
\\ %5C\
/ %2F\
| %7C\
: %3A\
{;} %3B\
{"} %22\
< %3C\
> %3E\
]
#above have some overlap with ctrl codes below.
#no big deal as it's a dict
#must_encode
# + # * ? \ / | : ; " < > <sp> \t
# also NUL to empty string
# also ctrl chars 01 to 1F (1..31)
for {set i 1} {$i < 32} {incr i} {
set ch [format %c $i]
set enc "%[format %02X $i]"
set enc_lower [string tolower $enc]
dict set encode_map $ch $enc
dict set decode_map $enc $ch
dict set decode_map $enc_lower $ch
}
variable must_encode
set must_encode [dict keys $encode_map]
#if they are in
#decode map doesn't include
# %00 (nul)
# %2F "/"
# %2f "/"
# %7f (del)
#we exlude the forward slash because we already have + for that - and multiple ways to specify it obscure intention.
#
set decode_map [dict merge $decode_map [dict create\
%09 \t\
%20 { }\
%21 "!"\
%22 {"}\
%23 "#"\
%24 "$"\
%25 "%"\
%26 "&"\
%27 "'"\
%28 "("\
%29 ")"\
%2A "*"\
%2a "*"\
%2B "+"\
%2b "+"\
%2C ","\
%2c ","\
%2D "-"\
%2d "-"\
%2E "."\
%2e "."\
%3A ":"\
%3a ":"\
%3B {;}\
%3b {;}\
%3D "="\
%3C "<"\
%3c "<"\
%3d "="\
%3E ">"\
%3e ">"\
%3F "?"\
%3f "?"\
%40 "@"\
%5B "\["\
%5b "\["\
%5C "\\"\
%5c "\\"\
%5D "\]"\
%5d "\]"\
%5E "^"\
%5e "^"\
%60 "`"\
%7B "{"\
%7b "{"\
%7C "|"\
%7c "|"\
%7D "}"\
%7d "}"\
%7E "~"\
%7e "~"\
]]
#Don't go above 7f
#if we want to specify p
#*** !doctools
#[subsection {Namespace fauxlink}]
#[para] Core API functions for fauxlink
#[list_begin definitions]
proc Segment_mustencode_check {str} {
variable decode_map
variable encode_map ;#must_encode
set idx 0
set err ""
foreach ch [split $str ""] {
if {[dict exists $encode_map $ch]} {
set enc [dict get $encode_map $ch]
if {[dict exists $decode_map $enc]} {
append err " char $idx should be encoded as $enc" \n
} else {
append err " no %xx encoding available. Use %UXX if really required" \n
}
}
incr idx
}
return $err ;#empty string if ok
}
proc resolve {link} {
variable decode_map
variable encode_map
variable must_encode
set ftail [file tail $link]
set extension_name [string range [file extension $ftail] 1 end]
if {$extension_name ni [list fxlnk fauxlink]} {
set is_fauxlink 0
#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
#(e.g blindly processing all files in a folder that is normally only .fxlnk files - but then something added that happens
# to have # characters in it)
#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.
#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"
} else {
set is_fauxlink 1
set err_extra ""
}
set linkspec [file rootname $ftail]
# - any # or + within the target path or name should have been uri encoded as %23 and %2b
if {[tcl::string::first # $linkspec] < 0} {
set err "fauxlink::resolve '$link'. Link must contain a # (usually at start if name matches target)"
append err $err_extra
error $err
}
#The 1st 2 parts of split on # are name and target file/dir
#If there are only 3 parts the 3rd part is a comment and there are no 'tags'
#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
#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
#has a name, a target, 2 tags and one comment
#check namespec already has required chars encoded
set segments [split $linkspec #]
lassign $segments namespec targetspec
#puts stderr "-->namespec $namespec"
set nametest [tcl::string::map $encode_map $namespec]
#puts stderr "-->nametest $nametest"
#nothing should be changed - if there are unencoded chars that must be encoded it is an error
if {[tcl::string::length $nametest] ne [tcl::string::length $namespec]} {
set err "fauxlink::resolve '$link' invalid chars in name part (section prior to first #)"
append err [Segment_mustencode_check $namespec]
append err $err_extra
error $err
}
#see comments below regarding 2 rounds and ordering.
set name [decode_unicode_escapes $namespec]
set name [tcl::string::map $decode_map $name]
#puts stderr "-->name: $name"
set targetsegment [split $targetspec +]
#check each + delimited part of targetspec already has required chars encoded
set pp 0 ;#pathpart index
set targetpath_parts [list]
foreach pathpart $targetsegment {
set targettest [tcl::string::map $encode_map $pathpart]
if {[tcl::string::length $targettest] ne [tcl::string::length $pathpart]} {
set err "fauxlink::resolve '$link' invalid chars in targetpath (section following first #)"
append err [Segment_mustencode_check $pathpart]
append err $err_extra
error $err
}
#2 rounds of substitution is possibly asking for trouble..
#We allow anything in the resultant segments anyway (as %UXXXX... allows all)
#so it's not so much about what can be encoded,
# - but it makes it harder to reason about for users
# In particular - if we map %XX first it makes %25 -> % substitution tricky
# if the user requires a literal %UXXX - they can't do %25UXXX
# the double sub would make it %UXXX -> somechar anyway.
#we do unicode first - as a 2nd round of %XX substitutions is unlikely to interfere.
#There is still the opportunity to use things like %U00000025 followed by hex-chars
# and get some minor surprises, but using %U on ascii is unlikely to be done accidentally - REVIEW
set pathpart [decode_unicode_escapes $pathpart]
set pathpart [tcl::string::map $decode_map $pathpart]
lappend targetpath_parts $pathpart
incr pp
}
set targetpath [join $targetpath_parts /]
if {$name eq ""} {
set name [lindex $targetpath_parts end]
}
#we do the same encoding checks on tags and comments to increase chances of portability
set tags [list]
set comments [list]
switch -- [llength $segments] {
2 {
#no tags or comments
}
3 {
#only 3 sections - last is comment - even if looks like tags
#to make the 3rd part a tagset, an extra # would be needed
set comments [list [lindex $segments 2]]
}
default {
set tagset [lindex $segments 2]
if {$tagset eq ""} {
#ok - no tags
} else {
if {[string first @ $tagset] != 0} {
set err "fauxlink::resolve '$link' invalid tagset in 3rd #-delimited segment"
append err \n " - must begin with @"
append err $err_extra
error $err
} else {
set tagset [string range $tagset 1 end]
set rawtags [split $tagset @]
set tags [list]
foreach t $rawtags {
if {$t eq ""} {
lappend tags ""
} else {
set tagtest [tcl::string::map $encode_map $t]
if {[tcl::string::length $tagtest] ne [tcl::string::length $t]} {
set err "fauxlink::resolve '$link' invalid chars in tag [llength $tags]"
append err [Segment_mustencode_check $t]
append err $err_extra
error $err
}
lappend tags [tcl::string::map $decode_map [decode_unicode_escapes $t]]
}
}
}
}
set rawcomments [lrange $segments 3 end]
#set comments [lsearch -all -inline -not $comments ""]
set comments [list]
foreach c $rawcomments {
if {$c eq ""} {continue}
set commenttest [tcl::string::map $encode_map $c]
if {[tcl::string::length $commenttest] ne [tcl::string::length $c]} {
set err "fauxlink::resolve '$link' invalid chars in comment [llength $comments]"
append err [Segment_mustencode_check $c]
append err $err_extra
error $err
}
lappend comments [tcl::string::map $decode_map [decode_unicode_escapes $c]]
}
}
}
set data [dict create name $name targetpath $targetpath tags $tags comments $comments fauxlinkextension $extension_name]
if {$is_fauxlink} {
#standard .fxlnk or .fauxlink
return $data
} else {
#custom extension - or called in error on wrong type of file but happened to parse.
#see comments at top regarding is_fauxlink
#make sure no keys in common at top level.
return [dict create\
linktype $extension_name\
note "nonstandard extension returning nonstandard dict with result in data key"\
data $data\
]
}
}
variable map
#default exclusion of / (%U2f and equivs)
#this would allow obfuscation of intention - when we have + for that anyway
proc decode_unicode_escapes {str {exclusions {/ \n \r \x00}}} {
variable map
set ucstart [string first %U $str 0]
if {$ucstart < 0} {
return $str
}
set max 8
set map [list]
set strend [expr {[string length $str]-1}]
while {$ucstart >= 0} {
set s $ucstart
set i [expr {$s +2}] ;#skip the %U
set hex ""
while {[tcl::string::length $hex] < 8 && $i <= $strend} {
set in [string index $str $i]
if {[tcl::string::is xdigit -strict $in]} {
append hex $in
} else {
break
}
incr i
}
if {$hex ne ""} {
incr i -1
lappend map $s $i $hex
}
set ucstart [tcl::string::first %U $str $i]
}
set out ""
set lastidx -1
set e 0
foreach {s e hex} $map {
append out [string range $str $lastidx+1 $s-1]
set sub [format %c 0x$hex]
if {$sub in $exclusions} {
append out %U$hex ;#put it back
} else {
append out $sub
}
set lastidx $e
}
if {$e < [tcl::string::length $str]-1} {
append out [string range $str $e+1 end]
}
return $out
}
proc link_as {name target} {
}
#proc sample1 {p1 args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [opt {?option value...?}]]
# #[para]Description of sample1
# return "ok"
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace fauxlink ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval fauxlink::lib {
namespace export {[a-z]*}; # Convention: export all lowercase
namespace path [namespace parent]
#*** !doctools
#[subsection {Namespace fauxlink::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 fauxlink::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
namespace eval fauxlink::system {
#*** !doctools
#[subsection {Namespace fauxlink::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide fauxlink [namespace eval fauxlink {
variable pkg fauxlink
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

21
src/bootsupport/modules/fauxlink-0.1.1.tm

@ -20,7 +20,7 @@
#[manpage_begin fauxlink_module_fauxlink 0 0.1.1]
#[copyright "2024"]
#[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]
#[keywords symlink faux fake shortcut toml]
#[description]
@ -29,18 +29,19 @@
#[para] A solution is required for application-driven filesystem links that survives cross platform moves as well as
#[para] archiving and packaging systems.
#[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] The file extension must be .fauxlink or .fxlnk
#[para] The + symbol substitutes for forward-slashes.
#[para] Other chars can be encoded using url-like encoding - (but only up to %7E !)
#[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] 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] file%23A.txt#..+file%23A.txt.fxlnk
#[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fxlnk
#[para] file%23A.txt#..+file%23A.txt.fauxlink
#[para] or equivalently (but obviously affecting sorting) #..+file%23A.txt.fauxlink
#[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 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
@ -63,9 +64,9 @@
#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.
#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
# "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)
# e.g
@ -296,12 +297,12 @@ namespace eval fauxlink {
set is_fauxlink 0
#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
#(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)
#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.
#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 {
set is_fauxlink 1
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 @
#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 @
#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
#check namespec already has required chars encoded

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

697
src/bootsupport/modules/modpod-0.1.1.tm

@ -1,697 +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.1
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin modpod_module_modpod 0 0.1.1]
#[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"
#}
#old tar connect mechanism - review - not needed?
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
#//review
set modpod [::modpod::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
}
}
#zipfile is a pure zip at this point - ie no script/exe header
proc make_zip_modpod {args} {
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.
'archive' relative offsets are easier to work with (for writing/updating) in tools such as 7zip,peazip,
but other tools may be easier with 'file' relative offsets. (e.g info-zip,pkzip)
info-zip's 'zip -A' can sometimes convert archive-relative to file-relative.
-offsettype archive is equivalent to plain 'cat prefixfile zipfile > modulefile'"
*values -min 2 -max 2
zipfile -type path -minlen 1 -help "path to plain zip file with subfolder #modpod-packagename-version containing .tm, data files and/or binaries"
outfile -type path -minlen 1 -help "path to output file. Name should be of the form packagename-version.tm"
} $args]
set zipfile [dict get $argd values zipfile]
set outfile [dict get $argd values outfile]
set opt_offsettype [dict get $argd opts -offsettype]
set mount_stub [string map [list %offsettype% $opt_offsettype] {
#zip file with Tcl loader prepended. Requires either builtin zipfs, or vfs::zip to mount while zipped.
#Alternatively unzip so that extracted #modpod-package-version folder is in same folder as .tm file.
#generated using: modpod::lib::make_zip_modpod -offsettype %offsettype% <zipfile> <tmfile>
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 properly 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 (and zipfs not available either)"
append msg \n "If neither zipfs or vfs::zip are available - 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 supplied zipfile has #modpod-loadcript.tcl or some other script/executable before even creating?
append mount_stub \x1A
modpod::system::make_mountable_zip $zipfile $outfile $mount_stub $opt_offsettype
}
#*** !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
#zipfile here is plain zip - no script/exe prefix part.
proc make_mountable_zip {zipfile outfile mount_stub {offsettype "file"}} {
set inzip [open $zipfile r]
fconfigure $inzip -encoding iso8859-1 -translation binary
set out [open $outfile w+]
fconfigure $out -encoding iso8859-1 -translation binary
puts -nonewline $out $mount_stub
set stuboffset [tell $out]
lappend report "sfx stub size: $stuboffset"
fcopy $inzip $out
close $inzip
set size [tell $out]
lappend report "tmfile : [file tail $outfile]"
lappend report "output size : $size"
lappend report "offsettype : $offsettype"
if {$offsettype eq "file"} {
#make zip offsets relative to start of whole file including prepended script.
#(same offset structure as Tcl's 'zipfs mkimg' as at 2024-10)
#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:
#The structure itself is 24 bytes Long, followed by a maximum of 64Kbytes text
if {$size < 65559} {
set tailsearch_start 0
} else {
set tailsearch_start [expr {$size - 65559}]
}
seek $out $tailsearch_start
set data [read $out]
#EOCD - End of Central Directory record
#PK\5\6
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
set filerelative_eocd_posn [expr {$start_of_end + $tailsearch_start}]
lappend report "kitfile-relative START-OF-EOCD: $filerelative_eocd_posn"
seek $out $filerelative_eocd_posn
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 {$filerelative_eocd_posn+16}]
#adjust offset of start of central directory by the length of our sfx stub
puts -nonewline $out [binary format i [expr {$eocd(diroffset) + $stuboffset}]]
flush $out
seek $out $filerelative_eocd_posn
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)"
#PK\1\2
#33639248 dec = 0x02014b50 - central directory 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)+$stuboffset}]]
#verify:
flush $out
seek $out $current_file
set fileheader [read $out 46]
lappend report "old $x(offset) + $stuboffset"
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.1
}]
return
#*** !doctools
#[manpage_end]

1894
src/bootsupport/modules/natsort-0.1.1.5.tm

File diff suppressed because it is too large Load Diff

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

@ -2469,7 +2469,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
if {$pretty} {
#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 {
@ -5116,6 +5116,7 @@ tcl::namespace::eval punk::ansi::ta {
# 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)
#regexp expanded syntax = ?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))))
|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)

65
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
#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 projectbase [dict get $projectinfo closest]
#set projectinfo [punk::repo::find_repos $tmfolder] ;#slow - REVIEW
#set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $tmfolder]
#store the projectbase even if it's empty string
set extended_capdict $capdict
set resolved_path [file join $tmfolder $path]
@ -148,8 +150,9 @@ namespace eval punk::cap::handlers::templates {
return 0
}
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest]
#set projectinfo [punk::repo::find_repos $shellbase]
#set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict
dict set extended_capdict vendor $vendor
@ -166,8 +169,9 @@ namespace eval punk::cap::handlers::templates {
return 0
}
set shellbase [file dirname [file dirname [file normalize [set ::argv0]/__]]] ;#review
set projectinfo [punk::repo::find_repos $shellbase]
set projectbase [dict get $projectinfo closest]
#set projectinfo [punk::repo::find_repos $shellbase]
#set projectbase [dict get $projectinfo closest]
set projectbase [punk::repo::find_project $shellbase]
set extended_capdict $capdict
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"
return 0
}
set projectinfo [punk::repo::find_repos $normpath]
set projectbase [dict get $projectinfo closest]
#set projectinfo [punk::repo::find_repos $normpath]
#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
set extended_capdict $capdict
@ -244,6 +249,18 @@ namespace eval punk::cap::handlers::templates {
# -- --- --- --- --- --- ---
namespace export *
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 {
#return a dict keyed on folder with source pkg as value
constructor {capname} {
@ -253,11 +270,8 @@ namespace eval punk::cap::handlers::templates {
set capabilityname $capname
}
method folders {args} {
set argd [punk::args::get_dict {
@id -id "::punk::cap::handlers::templates::class::api folders"
-startdir -default ""
@values -max 0
} $args]
#puts "--folders $args"
set argd [punk::args::parse $args withid "::punk::cap::handlers::templates::class::api folders"]
set opts [dict get $argd opts]
set opt_startdir [dict get $opts -startdir]
@ -270,6 +284,10 @@ namespace eval punk::cap::handlers::templates {
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
@ -314,9 +332,9 @@ namespace eval punk::cap::handlers::templates {
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]
} elseif {$pathtype eq "currentproject_multivendor"} {
set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest]
#set searchbase $startdir
#set pathinfo [punk::repo::find_repos $searchbase]
#set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} {
set deckbase [file join $pwd_projectroot $path]
if {![file exists $deckbase]} {
@ -349,9 +367,9 @@ namespace eval punk::cap::handlers::templates {
}
}
} elseif {$pathtype eq "currentproject"} {
set searchbase $startdir
set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest]
#set searchbase $startdir
#set pathinfo [punk::repo::find_repos $searchbase]
#set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} {
#path relative to projectroot already validated by handler as being within a currentproject_multivendor tree
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 layoutdict [dict create]
set projectinfo [punk::repo::find_repos $searchbase]
set projectroot [dict get $projectinfo closest]
#set projectinfo [punk::repo::find_repos $searchbase]
#set projectroot [dict get $projectinfo closest]
set projectroot [punk::repo::find_project $searchbase]
dict for {layoutname refinfo} $refdict {
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

5
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 {
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]} {
dict set pathinfo cksum ""
} else {
@ -851,7 +853,7 @@ namespace eval punk::mix::base {
}
} 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
set targetpath [file normalize $specifiedpath]
set storedpath $targetpath
@ -911,6 +913,7 @@ namespace eval punk::mix::base {
}
#buildruntime.exe obsolete..
puts stderr "warning obsolete? get_all_vfs_build_cksums 'buildruntime.exe'???"
set fullpath_buildruntime $buildfolder/buildruntime.exe
set ckinfo_buildruntime [cksum_path $fullpath_buildruntime]

18
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 repos [dict get $repopaths 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 {
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
if {"fossil" in $repotypes} {
#review - multiple process launches to fossil a bit slow on windows..
@ -739,7 +739,7 @@ namespace eval punk::mix::cli {
}
} else {
puts -nonewline stderr "."
puts -nonewline stderr "P"
set did_skip 1
#set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$build_event targetset_end SKIPPED
@ -771,7 +771,7 @@ namespace eval punk::mix::cli {
$event targetset_end OK -note "zip modpod"
}
} else {
puts -nonewline stderr "."
puts -nonewline stderr "p"
set did_skip 1
if {$is_interesting} {
puts stderr "$modulefile [$event targetset_source_changes]"
@ -893,7 +893,7 @@ namespace eval punk::mix::cli {
if {$is_interesting} {
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 file_record [punkcheck::installfile_skipped_install $basedir $file_record]
$event targetset_end SKIPPED
@ -935,7 +935,7 @@ namespace eval punk::mix::cli {
#set file_record [punkcheck::installfile_finished_install $basedir $file_record]
$event targetset_end OK -note "already versioned module"
} else {
puts -nonewline stderr "."
puts -nonewline stderr "f"
set did_skip 1
if {$is_interesting} {
puts stderr "$current_source_dir/$m [$event targetset_source_changes]"
@ -952,6 +952,7 @@ namespace eval punk::mix::cli {
set subdirs [list]
} else {
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"
foreach d $subdirs {
@ -965,7 +966,10 @@ namespace eval punk::mix::cli {
if {$skipdir} {
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
}
lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\

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

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

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

@ -1032,6 +1032,7 @@ namespace eval punk::mix::commandset::project {
set path [string trim [string range $pr 5 end]]
set nm [file rootname [file tail $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]
#strip "ckout:"
foreach ck $ckouts {
@ -1056,8 +1057,6 @@ namespace eval punk::mix::commandset::project {
}
@ -1067,11 +1066,6 @@ namespace eval punk::mix::commandset::project {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project {

26
src/bootsupport/modules/punk/mix/commandset/repo-0.1.0.tm

@ -24,6 +24,9 @@
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::mix::commandset::repo {
namespace export *
variable PUNKARGS
proc tickets {{project ""}} {
#todo
set result ""
@ -52,9 +55,9 @@ namespace eval punk::mix::commandset::repo {
set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos]
if {![llength $repos]} {
append result [dict get $repopaths warnings]
append result [a+ bold yellow][dict get $repopaths warnings][a]
} else {
append result [dict get $repopaths warnings]
append result [a+ bold yellow][dict get $repopaths warnings][a]
lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} {
append result \n "Fossil repo based at $repopath"
@ -69,6 +72,17 @@ namespace eval punk::mix::commandset::repo {
}
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 ""}} {
set searchbase [pwd]
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
}

3
src/bootsupport/modules/punk/mod-0.1.tm

@ -76,7 +76,7 @@ namespace eval punk::mod::cli {
set sorted_versions [lsort $versions]
set latest [lindex $sorted_versions 0]
if {$latest eq "" && [llength $sorted_versions] > 1} {
set latest [lindex $sorted_versions 1
set latest [lindex $sorted_versions 1]
}
dict set appinfo latest $latest
@ -157,7 +157,6 @@ namespace eval punk::mod::cli {
package provide punk::mod [namespace eval punk::mod {
variable version
set version 0.1
}]

21
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/* (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
@ -681,6 +682,7 @@ namespace eval punk::path {
set tailglobs [dict get $values tailglobs]
# -- --- --- --- --- --- ---
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]
# -- --- --- --- --- --- ---
# -- --- --- --- --- --- ---
@ -718,7 +720,24 @@ namespace eval punk::path {
puts stderr "treefilenames error while listing files in dir $opt_dir\n $matches"
set dirfiles [list]
} 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

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

@ -128,8 +128,7 @@ namespace eval punk::repo {
lappend PUNKARGS [list {
@dynamic
@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]}}
} ""]
lappend PUNKARGS [list {
@ -170,7 +169,7 @@ namespace eval punk::repo {
if {$fossilcmd ni $no_prompt_commands} {
set fossilrepos [dict get $repostate fossil]
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 "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"]
@ -217,7 +216,7 @@ namespace eval punk::repo {
}
} elseif {$fossilcmd in [list "info" "status"]} {
#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]
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 {}}} {
if {$path eq {}} { set path [pwd] }
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] }
#from kettle::path::is.fossil
foreach control {
@ -348,20 +366,51 @@ namespace eval punk::repo {
}
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?
#consider git rev-parse --git-dir ?
proc is_git_root {{path {}}} {
if {$path eq {}} { set path [pwd] }
set control [file join $path .git]
expr {[file exists $control] && [file isdirectory $control]}
#set control [file join $path .git]
#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 {}}} {
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 {}}} {
if {$path eq {}} { set path [pwd] }
if {[file pathtype $path] eq "relative"} {
@ -380,23 +429,33 @@ namespace eval punk::repo {
}
#review - adjust to allow symlinks to folders?
foreach required {
src
} {
set req $path/$required
if {(![file exists $req]) || ([file type $req] ne "directory") } {return 0}
#foreach required {
# src
#} {
# set req $path/$required
# 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 *]
#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
}
foreach sub $src_subs {
if {[string match *.vfs $sub]} {
#bare minimum2
# - 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
#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
@ -417,9 +476,17 @@ namespace eval punk::repo {
proc is_project_root {path} {
#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?
#(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
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
}
#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
#Treat an untracked 'candidate' folder as a sort of repository
proc find_repos {path} {
puts "find_repos '$path'"
set start_dir $path
#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]]]} {
lappend fossils_bottom_to_top $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
@ -868,6 +940,9 @@ namespace eval punk::repo {
while {[string length [set gitroot [punk::repo::find_git $git_search_from]]]} {
lappend gits_bottom_to_top $gitroot
set git_search_from [file dirname $gitroot]
if {$git_search_from eq $gitroot} {
break
}
}
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]]]} {
lappend candidates_bottom_to_top $candroot
set cand_search_from [file dirname $candroot]
if {$cand_search_from eq $candroot} {
break
}
}
dict set root_dict candidate $candidates_bottom_to_top

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

@ -243,12 +243,14 @@ namespace eval punkcheck {
}
method get_targets_exist {} {
set punkcheck_folder [file dirname [$o_installer get_checkfile]]
set existing [list]
foreach t $o_targets {
if {[file exists [file join $punkcheck_folder $t]]} {
lappend existing $t
}
}
set existing [glob -nocomplain -dir $punkcheck_folder -tails {*}$o_targets]
#set existing [list]
#foreach t $o_targets {
# if {[file exists [file join $punkcheck_folder $t]]} {
# lappend existing $t
# }
#}
return $existing
}
method end {} {
@ -880,19 +882,46 @@ namespace eval punkcheck {
#allow nonexistant as a source
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 fsize ""
} else {
set ftype [file type $fpath]
if {$ftype eq "directory"} {
if {[llength $dir_set]} {
set ftype "directory"
set fsize "NA"
} elseif {[llength $link_set]} {
set ftype "link"
set fsize 0
} else {
set ftype "file"
#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]
}
}
#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
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]]
@ -1648,6 +1677,10 @@ namespace eval punkcheck {
set is_skip 0
if {$overwrite_what eq "all-targets"} {
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
lappend files_copied $current_source_dir/$m
} 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]
}
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 ""
if {[dict size $resultdict]} {
set copied [dict get $resultdict files_copied]
append msg "--------------------------" \n
append msg "[dict keys $resultdict]" \n
if {[llength $copied] == 0} {
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 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 {
append msg "COPIED [punkcheck::lib::path_relative $checkfolder $f]" \n
append msg " TO $tgtdir" \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 "--------------------------" \n
append msg $ruler \n
}
return $msg
}

BIN
src/bootsupport/modules/test/tomlish-1.1.1.tm

Binary file not shown.

BIN
src/bootsupport/modules/test/tomlish-1.1.3.tm

Binary file not shown.

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

File diff suppressed because it is too large Load Diff

8520
src/bootsupport/modules/textblock-0.1.2.tm

File diff suppressed because it is too large Load Diff

150
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 ="
}
ITABLE {
#This one should not be returned as a type <tag> value <something> structure!
#
set result [::tomlish::to_dict [list $found_sub]]
}
ARRAY {
@ -249,6 +251,7 @@ namespace eval tomlish {
}
#to_dict is a *basic* programmatic datastructure for accessing the data.
# produce a dictionary of keys and values from a tomlish tagged list.
# 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'
#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.
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'<<<"
set items $tomlish
@ -345,6 +352,9 @@ namespace eval tomlish {
set keyval_dict [_get_keyval_value $item]
dict set datastructure {*}$pathkeys $leafkey $keyval_dict
#JMN test 2025
}
TABLE {
set tablename [lindex $item 1]
@ -386,8 +396,40 @@ namespace eval tomlish {
lappend table_key_hierarchy_raw $rawseg
if {[dict exists $datastructure {*}$table_key_hierarchy]} {
#It's ok for this key to already exist *if* it was defined by a previous tablename,
# but not if it was defined as a key/qkey/skey ?
#It's ok for this key to already exist *if* it was defined by a previous tablename or equivalent
#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 .]
@ -422,7 +464,7 @@ namespace eval tomlish {
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
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 {
append msg " " $ts \n
}
@ -453,13 +495,18 @@ namespace eval tomlish {
#now add the contained elements
foreach element [lrange $item 2 end] {
set type [lindex $element 0]
log::debug "--> $type processing contained element $element"
switch -exact -- $type {
DOTTEDKEY {
set dkey_info [_get_dottedkey_info $element]
#e.g1 keys {x.y y} keys_raw {'x.y' y}
#e.g2 keys {x.y y} keys_raw {{"x.y"} y}
set dotted_key_hierarchy [dict get $dkey_info keys]
set dotted_key_hierarchy_raw [dict get $dkey_info keys_raw]
set leaf_key [lindex $dotted_key_hierarchy end]
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
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."
}
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
#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 {
#obsolete ?
@ -777,7 +839,7 @@ namespace eval tomlish {
set result [list]
set lastparent [lindex $parents end]
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]
#treat ITABLE differently?
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo]
@ -811,7 +873,7 @@ namespace eval tomlish {
} else {
set VK_PART [list KEY $vk]
}
if {[tomlish::dict::is_tomltype $vv]} {
if {[tomlish::dict::is_tomlish_typeval $vv]} {
#type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv]
set record [list DOTTEDKEY [list $VK_PART {WS { }}] = {WS { }} $sublist]
@ -877,7 +939,7 @@ namespace eval tomlish {
}
} else {
#lastparent is not toplevel "" or "do_inline"
if {[tomlish::dict::is_tomltype $vinfo]} {
if {[tomlish::dict::is_tomlish_typeval $vinfo]} {
#type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vinfo]
lappend result {*}$sublist
@ -901,7 +963,7 @@ namespace eval tomlish {
} else {
set VK_PART [list KEY $vk]
}
if {[tomlish::dict::is_tomltype $vv]} {
if {[tomlish::dict::is_tomlish_typeval $vv]} {
#type x value y
set sublist [_from_dictval_tomltype $parents $tablestack $keys $vv]
set record [list DOTTEDKEY [list $VK_PART] = $sublist]
@ -2404,7 +2466,8 @@ namespace eval tomlish::utils {
} ;#RS
#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} {
return 0
} 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'.
proc int_validchars1 {str} {
set numchars [tcl::string::length $str]
@ -3471,7 +3580,7 @@ namespace eval tomlish::parse {
return 1
}
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 {
# hash marks end of whitespace token
@ -5222,7 +5331,7 @@ namespace eval tomlish::parse {
if {[tomlish::utils::is_barekey $c]} {
append tok $c
} 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 {
@ -5354,10 +5463,15 @@ namespace eval tomlish::dict {
namespace export {[a-z]*}; # Convention: export all lowercase
namespace path [namespace parent]
proc is_tomltype {d} {
expr {[dict size $d] == 2 && [dict exists $d type] && [dict exists $d value]}
proc is_tomlish_typeval {d} {
#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
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}]
foreach k [lreverse [dict keys $d]] {
set dval [dict get $d $k]
if {[is_tomltype $dval]} {
if {[is_tomlish_typeval $dval]} {
set last_simple $dictposn
break
}

1382
src/bootsupport/modules/tomlish-1.1.1.tm → src/bootsupport/modules/tomlish-1.1.3.tm

File diff suppressed because it is too large Load Diff

BIN
src/bootsupport/modules/zipper-0.11.tm

Binary file not shown.

70
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.
#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 "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline
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} {
#only forget all *unloaded* package names
foreach pkg [package names] {
@ -282,7 +285,6 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
#package require Thread
# - 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
#These are strong dependencies
package forget punk::mix
@ -293,6 +295,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
package require punk::mix
package require punkcheck
package require punk::lib
package require punk::args
package require punk::ansi
set package_paths_modified 1
@ -307,6 +311,7 @@ set ::punkboot::bootsupport_requirements [dict create\
punk::repo [list version "00.01.01-"]\
punk::mix [list version ""]\
punk::ansi [list]\
punk::args [list]\
overtype [list version "1.6.5-"]\
punkcheck [list]\
fauxlink [list version "0.1.1-"]\
@ -1389,10 +1394,9 @@ if {$::punkboot::command eq "vendorupdate"} {
#todo vendor/lib
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*]
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules_tcl*]
lappend vendormodulefolders vendormodules
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
#lappend vendormodulefolders vendormodules
foreach vf $vendormodulefolders {
if {[file exists $sourcefolder/$vf]} {
lassign [split $vf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
@ -1483,7 +1487,6 @@ if {$::punkboot::command eq "vendorupdate"} {
}
}
}
}
vendor_localupdate $projectroot
@ -1508,10 +1511,8 @@ if {$::punkboot::command eq "bootsupport"} {
set bootsupport_modules [list] ;#variable populated by include_modules.config file - review
set sourcefolder $projectroot/src
set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules_tcl*]
lappend bootmodulefolders modules
set bootmodulefolders [glob -nocomplain -dir $sourcefolder/bootsupport -type d -tails modules modules_tcl*]
foreach bm $bootmodulefolders {
if {[file exists $sourcefolder/bootsupport/$bm]} {
lassign [split $bm _] _bm tclx
if {$tclx ne ""} {
set which _$tclx
@ -1609,7 +1610,6 @@ if {$::punkboot::command eq "bootsupport"} {
}
}
}
}
bootsupport_localupdate $projectroot
@ -1699,10 +1699,28 @@ 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)
if {$::punkboot::command in {project modules}} {
set vendorlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails vendorlib_tcl*]
lappend vendorlibfolders vendorlib
set vendormodulefolders [glob -nocomplain -dir $sourcefolder -type d -tails vendormodules vendormodules_tcl*]
foreach vf $vendormodulefolders {
lassign [split $vf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
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]
}
if {![llength $vendormodulefolders]} {
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 {
if {[file exists $sourcefolder/$lf]} {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
@ -1723,34 +1741,10 @@ if {$::punkboot::command in {project modules}} {
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 {
if {[file exists $sourcefolder/$vf]} {
lassign [split $vf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
} else {
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]
}
}
if {![llength $vendormodulefolders]} {
puts stderr "VENDORMODULES: No src/vendormodules or src/vendormodules_tcl* folders found."
}
########################################################
#templates
@ -1823,7 +1817,6 @@ if {$::punkboot::command in {project modules}} {
set projectlibfolders [glob -nocomplain -dir $sourcefolder -type d -tails lib_tcl*]
lappend projectlibfolders lib
foreach lf $projectlibfolders {
if {[file exists $sourcefolder/$lf]} {
lassign [split $lf _] _vm tclx
if {$tclx ne ""} {
set which _$tclx
@ -1844,7 +1837,6 @@ if {$::punkboot::command in {project modules}} {
set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
}
}
if {![llength $projectlibfolders]} {
puts stderr "PROJECTLIB: No src/lib or src/lib_tcl* folder found."
}

Loading…
Cancel
Save