promotion outputs: punk::path 0.5.0 into bootsupport + _vfscommon.vfs
make.tcl modules + bootsupport + vfscommonupdate outputs for the path 0.5.0
arc (source commits ed17fb6c, 4d481e42): src/bootsupport/modules and
src/vfs/_vfscommon.vfs/modules move from path-0.4.0 to path-0.5.0
(superseded copies pruned with punkcheck DELETE records; promoted copies
byte-identical to the minted module).
Note: the vfscommonupdate PROVENANCE-WARNING emitted during this run
referred to the preceding bootsupport step's own outputs - batched here
per the build-outputs commit convention.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
#Continuation lines are deliberately unindented: placeholder substitution
#prepends the placeholder line's own indentation to every line after the
#first, so indented continuations here would render double-indented.
variable PATHGLOB_SEGMENT_SYNTAX {The glob elements *, **, *** and ? may appear in any segment of a path pattern:
* matches any single segment, or any span of characters within a segment
(/usr/*/bin matches /usr/local/bin but not /usr/bin or /usr/a/b/bin;
/usr/te*t matches /usr/test and /usr/teeeet but not /usr/te/t)
** as a whole segment matches 1 or more segments
(/usr/**/bin matches /usr/x/bin and /usr/x/y/bin but not /usr/bin.
Within a segment ** also spans segments: /usr/**.txt matches .txt files
at any depth below /usr)
*** as a whole segment matches 0 or more segments
(/usr/*** matches /usr itself as well as everything below it)
? matches any single character within a segment
(/usr/te?t matches /usr/test and /usr/text but not /usr/texxt or /usr/te/t)
* and ? are treated as glob characters wherever they appear in the pattern
- escaping is not supported. All other characters, including square brackets,
match as literals (unlike Tcl glob patterns).}
}
punk::args::define {
@id -id ::punk::path::pathglob_as_re
@cmd -name punk::path::pathglob_as_re -&
-summary -&
"Return a regular expression for matching paths to a *|**|***|? path glob pattern." -&
-help -&
"Returns an anchored regular expression string for matching a path to a
glob pattern which can contain the glob elements *|**|***|? in any
segment of the path structure.
${$::punk::path::argdoc::PATHGLOB_SEGMENT_SYNTAX}
The pathglob doesn't have to contain glob characters - a pathglob
without them produces a regex matching that path exactly.
Regular expression syntax is deliberately not supported within the
pathglob string - supplied regex characters are treated as literals.
This is the engine behind punk::path::globmatchpath and the path
pattern matching of the treefilenames/subfolders family."
@leaders -min 1 -max 1
pathglob -type string -optional 0 -help -&
"path glob pattern"
}
proc pathglob_as_re {pathglob} {
#see PUNKARGS id ::punk::path::pathglob_as_re (documentation-only - single positional arg)
#*** !doctools
#[call [fun pathglob_as_re] [arg pathglob]]
#[para] Returns a regular expression for matching a path to a glob pattern which can contain glob chars *|**|***|? in any segment of the path structure
@ -799,52 +918,40 @@ namespace eval punk::path {
punk::args::define {
@id -id ::punk::path::globmatchpath
@cmd -name punk::path::globmatchpath\
-summary\
"Match path to *|**|? glob patterns"\
-help\
"Return a boolean indicating whether the path matches the specialised glob pattern.
A pattern such as /usr/*/bin will match any path that has /usr as the first segment and bin as the third segment,
with any single segment in between.
A pattern such as /usr/**/bin will match any path that has /usr as the first segment and bin as the last segment,
with 1 or more segments in between (so it will not match /usr/bin).
A pattern such as /usr/** will match any path that has /usr as the first segment, with 1 or more segments
following (so it will not match /usr itself).
A pattern such as /usr/*** will match /usr itself as well as any path below /usr
- *** as a whole segment matches zero or more segments (added 2026-07-20, G-093).
A pattern such as **/*.txt will match any path that ends with .txt, with 1 or more leading segments
(so it will not match test.txt or .txt). Use ***/*.txt to also match a bare test.txt.
A pattern such as ** will match any path.
The glob characters * and ? (and the whole-segment forms ** and ***) are the only special characters in the pathglob syntax.
- they are treated as glob characters regardless of where they appear in the pathglob string.
Note that this is different from other Tcl glob contexts where square brackets can be used.
The pathglob syntax treats other characters, including square brackets as literals.
For example, the pattern /usr/te?t will match /usr/test and /usr/text but not /usr/texxt, and the pattern /usr/te*t
will match /usr/test, /usr/teat, and /usr/teeeet but not /usr/te/t.
The pathglob syntax does not support escaping of glob characters - any glob characters in the pathglob are treated
as glob characters. For example, the pattern /usr/* will match any path that has /usr as the first segment and any
single segment as the second segment, but there is no way to specify a pattern that matches any path that has /usr
as the first segment and a literal * as the second segment.
Caller must ensure that file separator is forward slash. (e.g use file normalize on windows)
options:
-nocase 0|1 (default 0 - case sensitive)
If -nocase is not supplied - default to case sensitive *except for driveletter*
ie - the driveletter alone in paths such as c:/etc will still be case insensitive. (ie c:/ETC/* will match C:/ETC/blah but not C:/etc/blah)
Explicitly specifying -nocase 0 will require the entire case to match including the driveletter.
"
@cmd -name punk::path::globmatchpath -&
-summary -&
"Match path to *|**|***|? glob patterns" -&
-help -&
"Return a boolean indicating whether the path matches the specialised
glob pattern.
${$::punk::path::argdoc::PATHGLOB_SEGMENT_SYNTAX}
Further whole-pattern examples:
/usr/** will match any path that has /usr as the first segment and 1
or more segments following (so it will not match /usr itself).
/usr/*** will match /usr itself as well as any path below /usr.
**/*.txt will match any path that ends with .txt, with 1 or more
leading segments (so it will not match test.txt or .txt). Use
***/*.txt to also match a bare test.txt.
** will match any path.
Caller must ensure that file separator is forward slash. (e.g use
file normalize on windows)"
@leaders
pathglob -type string -help "glob pattern to match path against. See [fun pathglob_as_re] for syntax of glob patterns"
pathglob -type string -help -&
"glob pattern to match path against (syntax as described above -
punk::path::pathglob_as_re documents the underlying conversion)"
path -type string -help "path to match against glob pattern"
@opts
-nocase -type boolean -default 0 -help\
-nocase -type boolean -default 0 -help -&
"case insensitive matching (default false - case sensitive)
- except for driveletter on windows which is always case insensitive
unless -nocase 0 is explicitly specified"
- except for the driveletter on windows paths: when -nocase is not
supplied, matching is case sensitive EXCEPT for a leading
driveletter (c:/ETC/* will match C:/ETC/blah but not C:/etc/blah).
Explicitly specifying -nocase 0 requires the entire case to match
A single /*/ will match any single segment in the path, and a single /**/ will match any number of segments in the path.
e.g to exclude any path with _aside as a segment in the middle: -exclude-paths **/_aside/**
i.e this would exclude /usr/_aside/etc and /usr/x/_aside/etc but not /usr/x/_aside or _aside/etc
To exclude all paths with _aside as a segment anywhere: -exclude-paths { **/_aside/** **/_aside _aside/**}
"
#todo -depth
@values -min 0 -max 1
path -type directory -optional 1 -help\
"Path of folder. If not supplied current directory is used.
This may be a relative or absolute path. Relative paths are treated as relative to current directory.
When using relative paths - the result will also be relative paths with the same relative prefix.
(e.g if path is ../test - the results will be ../test/subfolder1 ../test/subfolder2 etc)
Patterns in -exclude-paths are matched against the resulting paths
(so should be written to match the same relative prefix if path is relative)"
}
proc subfolders1 {args} {
#NOTE - this algorithm based on omit_only_patterns and prune_base_patterns was suggested by a 2026 AI model - it is apparent to this programmer that it is inadequate for the purpose.
#This can still return something like c:/repo/etc/src/vfs - which should be excluded by the pattern **/src/**
#todo - review and fix properly.
set argd [punk::args::parse $args withid ::punk::path::subfolders1]
lassign [dict values $argd] leaders opts values received
set do_recursion [dict exists $received -recursive]
set exclude_paths [dict get $opts -exclude-paths]
if {"**" in $exclude_paths} {
#if ** is in exclude_paths - then we can skip all glob matching and just return empty list
#This is likely user error - so we'll be loud about it for now but will still return empty list rather than erroring.
#If user code is building exclude_paths dynamically - they can check for this case themselves and avoid the call to subfolders1 to suppress this message.
puts stderr "punk::path::subfolders1 Warning - exclude_paths contains '**' - all paths will be excluded"
return [list]
}
if {[dict exists $received path]} {
set path [dict get $values path]
} else {
set path [pwd]
}
set all_subfolders [glob -nocomplain -directory $path -types d *]
#example of expected exclude_paths pattern behaviour when recursion is enabled:
# **/dirname -> omit /x/y/dirname, but still visit /x/y/dirname/*
# **/dirname/* -> include /x/y/dirname and /x/y/dirname/a/b but omit directories that are a single level below /x/y/dirname such as /x/y/dirname/a
#c:/** - would exclude all subfolders below c: but not c: itself
# **/test/** - would exclude any path with test as a segment and all its subfolders
#- but not paths with test as a segment that is the final segment
set folders [list]
set recurse_subdirs [list]
foreach f $all_subfolders {
set include_in_results 1
set allow_recurse 1
foreach pat $exclude_paths {
set pat_parts [file split $pat] ;#note file split c:/test gives {c:/ test} but file split **/test gives {** test}
#also note that file split on windows treats forward slashes and backslashes the same.
#by using file split, we gain some flexibility in syntax of paths and patterns,
#but lose the ability to use backslashes as escapes to allow literal glob characters in path segments.
#This is almost always a non-issue on windows since * and ? are not valid in path segments there, and is rarely an issue on unix even though
# * and ? are technically valid in path segments, but it is inadvisable there anyway for compatibility with shells etc.
#Continuation lines are deliberately unindented: placeholder substitution
#prepends the placeholder line's own indentation to every line after the
#first, so indented continuations here would render double-indented.
variable PATHGLOB_SEGMENT_SYNTAX {The glob elements *, **, *** and ? may appear in any segment of a path pattern:
* matches any single segment, or any span of characters within a segment
(/usr/*/bin matches /usr/local/bin but not /usr/bin or /usr/a/b/bin;
/usr/te*t matches /usr/test and /usr/teeeet but not /usr/te/t)
** as a whole segment matches 1 or more segments
(/usr/**/bin matches /usr/x/bin and /usr/x/y/bin but not /usr/bin.
Within a segment ** also spans segments: /usr/**.txt matches .txt files
at any depth below /usr)
*** as a whole segment matches 0 or more segments
(/usr/*** matches /usr itself as well as everything below it)
? matches any single character within a segment
(/usr/te?t matches /usr/test and /usr/text but not /usr/texxt or /usr/te/t)
* and ? are treated as glob characters wherever they appear in the pattern
- escaping is not supported. All other characters, including square brackets,
match as literals (unlike Tcl glob patterns).}
}
punk::args::define {
@id -id ::punk::path::pathglob_as_re
@cmd -name punk::path::pathglob_as_re -&
-summary -&
"Return a regular expression for matching paths to a *|**|***|? path glob pattern." -&
-help -&
"Returns an anchored regular expression string for matching a path to a
glob pattern which can contain the glob elements *|**|***|? in any
segment of the path structure.
${$::punk::path::argdoc::PATHGLOB_SEGMENT_SYNTAX}
The pathglob doesn't have to contain glob characters - a pathglob
without them produces a regex matching that path exactly.
Regular expression syntax is deliberately not supported within the
pathglob string - supplied regex characters are treated as literals.
This is the engine behind punk::path::globmatchpath and the path
pattern matching of the treefilenames/subfolders family."
@leaders -min 1 -max 1
pathglob -type string -optional 0 -help -&
"path glob pattern"
}
proc pathglob_as_re {pathglob} {
#see PUNKARGS id ::punk::path::pathglob_as_re (documentation-only - single positional arg)
#*** !doctools
#[call [fun pathglob_as_re] [arg pathglob]]
#[para] Returns a regular expression for matching a path to a glob pattern which can contain glob chars *|**|***|? in any segment of the path structure
@ -799,52 +918,40 @@ namespace eval punk::path {
punk::args::define {
@id -id ::punk::path::globmatchpath
@cmd -name punk::path::globmatchpath\
-summary\
"Match path to *|**|? glob patterns"\
-help\
"Return a boolean indicating whether the path matches the specialised glob pattern.
A pattern such as /usr/*/bin will match any path that has /usr as the first segment and bin as the third segment,
with any single segment in between.
A pattern such as /usr/**/bin will match any path that has /usr as the first segment and bin as the last segment,
with 1 or more segments in between (so it will not match /usr/bin).
A pattern such as /usr/** will match any path that has /usr as the first segment, with 1 or more segments
following (so it will not match /usr itself).
A pattern such as /usr/*** will match /usr itself as well as any path below /usr
- *** as a whole segment matches zero or more segments (added 2026-07-20, G-093).
A pattern such as **/*.txt will match any path that ends with .txt, with 1 or more leading segments
(so it will not match test.txt or .txt). Use ***/*.txt to also match a bare test.txt.
A pattern such as ** will match any path.
The glob characters * and ? (and the whole-segment forms ** and ***) are the only special characters in the pathglob syntax.
- they are treated as glob characters regardless of where they appear in the pathglob string.
Note that this is different from other Tcl glob contexts where square brackets can be used.
The pathglob syntax treats other characters, including square brackets as literals.
For example, the pattern /usr/te?t will match /usr/test and /usr/text but not /usr/texxt, and the pattern /usr/te*t
will match /usr/test, /usr/teat, and /usr/teeeet but not /usr/te/t.
The pathglob syntax does not support escaping of glob characters - any glob characters in the pathglob are treated
as glob characters. For example, the pattern /usr/* will match any path that has /usr as the first segment and any
single segment as the second segment, but there is no way to specify a pattern that matches any path that has /usr
as the first segment and a literal * as the second segment.
Caller must ensure that file separator is forward slash. (e.g use file normalize on windows)
options:
-nocase 0|1 (default 0 - case sensitive)
If -nocase is not supplied - default to case sensitive *except for driveletter*
ie - the driveletter alone in paths such as c:/etc will still be case insensitive. (ie c:/ETC/* will match C:/ETC/blah but not C:/etc/blah)
Explicitly specifying -nocase 0 will require the entire case to match including the driveletter.
"
@cmd -name punk::path::globmatchpath -&
-summary -&
"Match path to *|**|***|? glob patterns" -&
-help -&
"Return a boolean indicating whether the path matches the specialised
glob pattern.
${$::punk::path::argdoc::PATHGLOB_SEGMENT_SYNTAX}
Further whole-pattern examples:
/usr/** will match any path that has /usr as the first segment and 1
or more segments following (so it will not match /usr itself).
/usr/*** will match /usr itself as well as any path below /usr.
**/*.txt will match any path that ends with .txt, with 1 or more
leading segments (so it will not match test.txt or .txt). Use
***/*.txt to also match a bare test.txt.
** will match any path.
Caller must ensure that file separator is forward slash. (e.g use
file normalize on windows)"
@leaders
pathglob -type string -help "glob pattern to match path against. See [fun pathglob_as_re] for syntax of glob patterns"
pathglob -type string -help -&
"glob pattern to match path against (syntax as described above -
punk::path::pathglob_as_re documents the underlying conversion)"
path -type string -help "path to match against glob pattern"
@opts
-nocase -type boolean -default 0 -help\
-nocase -type boolean -default 0 -help -&
"case insensitive matching (default false - case sensitive)
- except for driveletter on windows which is always case insensitive
unless -nocase 0 is explicitly specified"
- except for the driveletter on windows paths: when -nocase is not
supplied, matching is case sensitive EXCEPT for a leading
driveletter (c:/ETC/* will match C:/ETC/blah but not C:/etc/blah).
Explicitly specifying -nocase 0 requires the entire case to match
A single /*/ will match any single segment in the path, and a single /**/ will match any number of segments in the path.
e.g to exclude any path with _aside as a segment in the middle: -exclude-paths **/_aside/**
i.e this would exclude /usr/_aside/etc and /usr/x/_aside/etc but not /usr/x/_aside or _aside/etc
To exclude all paths with _aside as a segment anywhere: -exclude-paths { **/_aside/** **/_aside _aside/**}
"
#todo -depth
@values -min 0 -max 1
path -type directory -optional 1 -help\
"Path of folder. If not supplied current directory is used.
This may be a relative or absolute path. Relative paths are treated as relative to current directory.
When using relative paths - the result will also be relative paths with the same relative prefix.
(e.g if path is ../test - the results will be ../test/subfolder1 ../test/subfolder2 etc)
Patterns in -exclude-paths are matched against the resulting paths
(so should be written to match the same relative prefix if path is relative)"
}
proc subfolders1 {args} {
#NOTE - this algorithm based on omit_only_patterns and prune_base_patterns was suggested by a 2026 AI model - it is apparent to this programmer that it is inadequate for the purpose.
#This can still return something like c:/repo/etc/src/vfs - which should be excluded by the pattern **/src/**
#todo - review and fix properly.
set argd [punk::args::parse $args withid ::punk::path::subfolders1]
lassign [dict values $argd] leaders opts values received
set do_recursion [dict exists $received -recursive]
set exclude_paths [dict get $opts -exclude-paths]
if {"**" in $exclude_paths} {
#if ** is in exclude_paths - then we can skip all glob matching and just return empty list
#This is likely user error - so we'll be loud about it for now but will still return empty list rather than erroring.
#If user code is building exclude_paths dynamically - they can check for this case themselves and avoid the call to subfolders1 to suppress this message.
puts stderr "punk::path::subfolders1 Warning - exclude_paths contains '**' - all paths will be excluded"
return [list]
}
if {[dict exists $received path]} {
set path [dict get $values path]
} else {
set path [pwd]
}
set all_subfolders [glob -nocomplain -directory $path -types d *]
#example of expected exclude_paths pattern behaviour when recursion is enabled:
# **/dirname -> omit /x/y/dirname, but still visit /x/y/dirname/*
# **/dirname/* -> include /x/y/dirname and /x/y/dirname/a/b but omit directories that are a single level below /x/y/dirname such as /x/y/dirname/a
#c:/** - would exclude all subfolders below c: but not c: itself
# **/test/** - would exclude any path with test as a segment and all its subfolders
#- but not paths with test as a segment that is the final segment
set folders [list]
set recurse_subdirs [list]
foreach f $all_subfolders {
set include_in_results 1
set allow_recurse 1
foreach pat $exclude_paths {
set pat_parts [file split $pat] ;#note file split c:/test gives {c:/ test} but file split **/test gives {** test}
#also note that file split on windows treats forward slashes and backslashes the same.
#by using file split, we gain some flexibility in syntax of paths and patterns,
#but lose the ability to use backslashes as escapes to allow literal glob characters in path segments.
#This is almost always a non-issue on windows since * and ? are not valid in path segments there, and is rarely an issue on unix even though
# * and ? are technically valid in path segments, but it is inadvisable there anyway for compatibility with shells etc.