#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[require punk::path]
#[require punk::path]
#[description]
#[description]
#[keywords module path filesystem]
#[keywords module path filesystem]
@ -104,21 +104,21 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[subsection {Namespace punk::path}]
#[subsection {Namespace punk::path}]
#[para] Core API functions for punk::path
#[para] Core API functions for punk::path
#[list_begin definitions]
#[list_begin definitions]
# -- ---
# -- ---
#punk::path::normjoin
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
#Our default is to allow trackback to:
# <scheme>://<something>
# <scheme>://<something>
# <driveletter>:/
# <driveletter>:/
@ -128,7 +128,7 @@ namespace eval punk::path {
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#
#The caller should do the file/vfs operations to determine this - not us.
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
@ -148,9 +148,9 @@ namespace eval punk::path {
#known issues:
#known issues:
#1)
#1)
# normjoin d://a//b//c -> d://a/b/c
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
# won't fix?
#2)
#2)
@ -164,16 +164,16 @@ namespace eval punk::path {
# normjoin ///server/share -> ///server/share
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
# possibly won't fix - review
#4) inconsistency
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# but other dos device paths are maintained
# e.g //./c:/etc
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
#
# ================
# ================
#
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
@ -194,14 +194,14 @@ namespace eval punk::path {
/// {
/// {
#if this is effectively //$emptyservername/
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
#we can't transform to // or /
return ///
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
#todo - shortcircuit that here?
}
}
}
}
# ///
# ///
set doubleslash1_posn [string first // $path]
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
#on windows //host is of type volumerelative
@ -221,7 +221,7 @@ namespace eval punk::path {
}
}
# -- --- ---
# -- --- ---
set is_relpath 0
set is_relpath 0
#set path [string map [list \\ /] $path]
#set path [string map [list \\ /] $path]
set finalparts [list]
set finalparts [list]
@ -264,11 +264,11 @@ namespace eval punk::path {
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
#assert parts here has {} {} as first 2 entries
set rootindex 2
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
#if {"zipfs:" eq [lindex $parts 2]} {
@ -281,7 +281,7 @@ namespace eval punk::path {
#set parts [file split $path]
#set parts [file split $path]
set parts [::split $path /]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
if {[lindex $parts 1] eq ""} {
@ -295,9 +295,9 @@ namespace eval punk::path {
} elseif {[lindex $parts 0] ne ""} {
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
#relpath a/b/c
set parts [linsert $parts 0 .]
set parts [linsert $parts 0 .]
set rootindex 0
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
#also need to stop possible conversion to absolute path
set is_relpath 1
set is_relpath 1
}
}
}
}
@ -306,7 +306,7 @@ namespace eval punk::path {
#puts stderr "-->baseparts:$baseparts"
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
#
set finalparts [list [lindex $baseparts 0]]
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
if {$b ni {. ..}} {
@ -333,7 +333,7 @@ namespace eval punk::path {
lappend finalparts $p
lappend finalparts $p
}
}
}
}
incr i
incr i
}
}
} else {
} else {
foreach p [lrange $parts $rootindex+1 end] {
foreach p [lrange $parts $rootindex+1 end] {
@ -345,7 +345,7 @@ namespace eval punk::path {
switch -exact -- $p {
switch -exact -- $p {
. - "" {}
. - "" {}
.. {
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
}
default {
default {
lappend finalparts $p
lappend finalparts $p
@ -403,16 +403,16 @@ namespace eval punk::path {
}
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# COM1 = COM1:
# //./COM1 ?? review
# //./COM1 ?? review
proc pathtype {str} {
proc pathtype {str} {
@ -425,7 +425,7 @@ namespace eval punk::path {
return absolute
return absolute
}
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
set firstslash [string first / $str]
if {$firstslash == -1} {
if {$firstslash == -1} {
@ -434,9 +434,9 @@ namespace eval punk::path {
set firstsegment [string range $str 0 $firstslash-1]
set firstsegment [string range $str 0 $firstslash-1]
}
}
if {[set firstc [string first : $firstsegment]] > 0} {
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
#count following / sequence
@ -466,7 +466,7 @@ namespace eval punk::path {
}
}
}
}
}
}
#assert first element of any return has been absolute or relative
#assert first element of any return has been absolute or relative
return relative
return relative
}
}
@ -489,7 +489,7 @@ namespace eval punk::path {
}
}
return $str
return $str
}
}
#purely string based - no reference to filesystem knowledge
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
#unix-style forward slash only
proc plainjoin {args} {
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set args [lmap a $args {string map "\\\\ /" $a}]
@ -499,12 +499,12 @@ namespace eval punk::path {
set out ""
set out ""
foreach a $args {
foreach a $args {
if {![string length $out]} {
if {![string length $out]} {
append out [plain $a]
append out [plain $a]
} else {
} else {
set a [plain $a]
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
set out [string range $out 0 end-1]
}
}
if {[string map {/ ""} $a] eq ""} {
if {[string map {/ ""} $a] eq ""} {
#all / segment
#all / segment
@ -512,16 +512,16 @@ namespace eval punk::path {
} else {
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
set a [string range $a 2 end]
}
}
if {[string index $out end] eq "/"} {
if {[string index $out end] eq "/"} {
append out $a
append out $a
} else {
} else {
append out / $a
append out / $a
}
}
}
}
}
}
}
}
return $out
return $out
}
}
proc plainjoin1 {args} {
proc plainjoin1 {args} {
if {[llength $args] == 1} {
if {[llength $args] == 1} {
@ -530,9 +530,9 @@ namespace eval punk::path {
set out [trim_final_slash [lindex $args 0]]
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
set a [trim_final_slash $a]
append out / $a
append out / $a
}
}
return $out
return $out
}
}
#intention?
#intention?
@ -554,13 +554,13 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[call [fun pathglob_as_re] [arg pathglob]]
#[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
#[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
#[para] ** matches any number of subdirectories.
#[para] ** matches any number of subdirectories.
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
@ -572,9 +572,9 @@ namespace eval punk::path {
}
}
switch -- $seg {
switch -- $seg {
* {lappend pats {[^/]*}}
* {lappend pats {[^/]*}}
** {lappend pats {.*}}
** {lappend pats {.*}}
default {
default {
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
**/_aside (exlude files where _aside is last segment)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
@values -min 0 -max -1 -optional 1 -type string
tailglobs -default * -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
within the directory tree being searched."
}
}
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[require punk::path]
#[require punk::path]
#[description]
#[description]
#[keywords module path filesystem]
#[keywords module path filesystem]
@ -104,21 +104,21 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[subsection {Namespace punk::path}]
#[subsection {Namespace punk::path}]
#[para] Core API functions for punk::path
#[para] Core API functions for punk::path
#[list_begin definitions]
#[list_begin definitions]
# -- ---
# -- ---
#punk::path::normjoin
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
#Our default is to allow trackback to:
# <scheme>://<something>
# <scheme>://<something>
# <driveletter>:/
# <driveletter>:/
@ -128,7 +128,7 @@ namespace eval punk::path {
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#
#The caller should do the file/vfs operations to determine this - not us.
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
@ -148,9 +148,9 @@ namespace eval punk::path {
#known issues:
#known issues:
#1)
#1)
# normjoin d://a//b//c -> d://a/b/c
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
# won't fix?
#2)
#2)
@ -164,16 +164,16 @@ namespace eval punk::path {
# normjoin ///server/share -> ///server/share
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
# possibly won't fix - review
#4) inconsistency
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# but other dos device paths are maintained
# e.g //./c:/etc
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
#
# ================
# ================
#
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
@ -194,14 +194,14 @@ namespace eval punk::path {
/// {
/// {
#if this is effectively //$emptyservername/
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
#we can't transform to // or /
return ///
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
#todo - shortcircuit that here?
}
}
}
}
# ///
# ///
set doubleslash1_posn [string first // $path]
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
#on windows //host is of type volumerelative
@ -221,7 +221,7 @@ namespace eval punk::path {
}
}
# -- --- ---
# -- --- ---
set is_relpath 0
set is_relpath 0
#set path [string map [list \\ /] $path]
#set path [string map [list \\ /] $path]
set finalparts [list]
set finalparts [list]
@ -264,11 +264,11 @@ namespace eval punk::path {
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
#assert parts here has {} {} as first 2 entries
set rootindex 2
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
#if {"zipfs:" eq [lindex $parts 2]} {
@ -281,7 +281,7 @@ namespace eval punk::path {
#set parts [file split $path]
#set parts [file split $path]
set parts [::split $path /]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
if {[lindex $parts 1] eq ""} {
@ -295,9 +295,9 @@ namespace eval punk::path {
} elseif {[lindex $parts 0] ne ""} {
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
#relpath a/b/c
set parts [linsert $parts 0 .]
set parts [linsert $parts 0 .]
set rootindex 0
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
#also need to stop possible conversion to absolute path
set is_relpath 1
set is_relpath 1
}
}
}
}
@ -306,7 +306,7 @@ namespace eval punk::path {
#puts stderr "-->baseparts:$baseparts"
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
#
set finalparts [list [lindex $baseparts 0]]
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
if {$b ni {. ..}} {
@ -333,7 +333,7 @@ namespace eval punk::path {
lappend finalparts $p
lappend finalparts $p
}
}
}
}
incr i
incr i
}
}
} else {
} else {
foreach p [lrange $parts $rootindex+1 end] {
foreach p [lrange $parts $rootindex+1 end] {
@ -345,7 +345,7 @@ namespace eval punk::path {
switch -exact -- $p {
switch -exact -- $p {
. - "" {}
. - "" {}
.. {
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
}
default {
default {
lappend finalparts $p
lappend finalparts $p
@ -403,16 +403,16 @@ namespace eval punk::path {
}
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# COM1 = COM1:
# //./COM1 ?? review
# //./COM1 ?? review
proc pathtype {str} {
proc pathtype {str} {
@ -425,7 +425,7 @@ namespace eval punk::path {
return absolute
return absolute
}
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
set firstslash [string first / $str]
if {$firstslash == -1} {
if {$firstslash == -1} {
@ -434,9 +434,9 @@ namespace eval punk::path {
set firstsegment [string range $str 0 $firstslash-1]
set firstsegment [string range $str 0 $firstslash-1]
}
}
if {[set firstc [string first : $firstsegment]] > 0} {
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
#count following / sequence
@ -466,7 +466,7 @@ namespace eval punk::path {
}
}
}
}
}
}
#assert first element of any return has been absolute or relative
#assert first element of any return has been absolute or relative
return relative
return relative
}
}
@ -489,7 +489,7 @@ namespace eval punk::path {
}
}
return $str
return $str
}
}
#purely string based - no reference to filesystem knowledge
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
#unix-style forward slash only
proc plainjoin {args} {
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set args [lmap a $args {string map "\\\\ /" $a}]
@ -499,12 +499,12 @@ namespace eval punk::path {
set out ""
set out ""
foreach a $args {
foreach a $args {
if {![string length $out]} {
if {![string length $out]} {
append out [plain $a]
append out [plain $a]
} else {
} else {
set a [plain $a]
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
set out [string range $out 0 end-1]
}
}
if {[string map {/ ""} $a] eq ""} {
if {[string map {/ ""} $a] eq ""} {
#all / segment
#all / segment
@ -512,16 +512,16 @@ namespace eval punk::path {
} else {
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
set a [string range $a 2 end]
}
}
if {[string index $out end] eq "/"} {
if {[string index $out end] eq "/"} {
append out $a
append out $a
} else {
} else {
append out / $a
append out / $a
}
}
}
}
}
}
}
}
return $out
return $out
}
}
proc plainjoin1 {args} {
proc plainjoin1 {args} {
if {[llength $args] == 1} {
if {[llength $args] == 1} {
@ -530,9 +530,9 @@ namespace eval punk::path {
set out [trim_final_slash [lindex $args 0]]
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
set a [trim_final_slash $a]
append out / $a
append out / $a
}
}
return $out
return $out
}
}
#intention?
#intention?
@ -554,13 +554,13 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[call [fun pathglob_as_re] [arg pathglob]]
#[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
#[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
#[para] ** matches any number of subdirectories.
#[para] ** matches any number of subdirectories.
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
@ -572,9 +572,9 @@ namespace eval punk::path {
}
}
switch -- $seg {
switch -- $seg {
* {lappend pats {[^/]*}}
* {lappend pats {[^/]*}}
** {lappend pats {.*}}
** {lappend pats {.*}}
default {
default {
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
**/_aside (exlude files where _aside is last segment)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
@values -min 0 -max -1 -optional 1 -type string
tailglobs -default * -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
within the directory tree being searched."
}
}
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[require punk::path]
#[require punk::path]
#[description]
#[description]
#[keywords module path filesystem]
#[keywords module path filesystem]
@ -104,21 +104,21 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[subsection {Namespace punk::path}]
#[subsection {Namespace punk::path}]
#[para] Core API functions for punk::path
#[para] Core API functions for punk::path
#[list_begin definitions]
#[list_begin definitions]
# -- ---
# -- ---
#punk::path::normjoin
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
#Our default is to allow trackback to:
# <scheme>://<something>
# <scheme>://<something>
# <driveletter>:/
# <driveletter>:/
@ -128,7 +128,7 @@ namespace eval punk::path {
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#
#The caller should do the file/vfs operations to determine this - not us.
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
@ -148,9 +148,9 @@ namespace eval punk::path {
#known issues:
#known issues:
#1)
#1)
# normjoin d://a//b//c -> d://a/b/c
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
# won't fix?
#2)
#2)
@ -164,16 +164,16 @@ namespace eval punk::path {
# normjoin ///server/share -> ///server/share
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
# possibly won't fix - review
#4) inconsistency
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# but other dos device paths are maintained
# e.g //./c:/etc
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
#
# ================
# ================
#
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
@ -194,14 +194,14 @@ namespace eval punk::path {
/// {
/// {
#if this is effectively //$emptyservername/
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
#we can't transform to // or /
return ///
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
#todo - shortcircuit that here?
}
}
}
}
# ///
# ///
set doubleslash1_posn [string first // $path]
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
#on windows //host is of type volumerelative
@ -221,7 +221,7 @@ namespace eval punk::path {
}
}
# -- --- ---
# -- --- ---
set is_relpath 0
set is_relpath 0
#set path [string map [list \\ /] $path]
#set path [string map [list \\ /] $path]
set finalparts [list]
set finalparts [list]
@ -264,11 +264,11 @@ namespace eval punk::path {
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
#assert parts here has {} {} as first 2 entries
set rootindex 2
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
#if {"zipfs:" eq [lindex $parts 2]} {
@ -281,7 +281,7 @@ namespace eval punk::path {
#set parts [file split $path]
#set parts [file split $path]
set parts [::split $path /]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
if {[lindex $parts 1] eq ""} {
@ -295,9 +295,9 @@ namespace eval punk::path {
} elseif {[lindex $parts 0] ne ""} {
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
#relpath a/b/c
set parts [linsert $parts 0 .]
set parts [linsert $parts 0 .]
set rootindex 0
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
#also need to stop possible conversion to absolute path
set is_relpath 1
set is_relpath 1
}
}
}
}
@ -306,7 +306,7 @@ namespace eval punk::path {
#puts stderr "-->baseparts:$baseparts"
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
#
set finalparts [list [lindex $baseparts 0]]
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
if {$b ni {. ..}} {
@ -333,7 +333,7 @@ namespace eval punk::path {
lappend finalparts $p
lappend finalparts $p
}
}
}
}
incr i
incr i
}
}
} else {
} else {
foreach p [lrange $parts $rootindex+1 end] {
foreach p [lrange $parts $rootindex+1 end] {
@ -345,7 +345,7 @@ namespace eval punk::path {
switch -exact -- $p {
switch -exact -- $p {
. - "" {}
. - "" {}
.. {
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
}
default {
default {
lappend finalparts $p
lappend finalparts $p
@ -403,16 +403,16 @@ namespace eval punk::path {
}
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# COM1 = COM1:
# //./COM1 ?? review
# //./COM1 ?? review
proc pathtype {str} {
proc pathtype {str} {
@ -425,7 +425,7 @@ namespace eval punk::path {
return absolute
return absolute
}
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
set firstslash [string first / $str]
if {$firstslash == -1} {
if {$firstslash == -1} {
@ -434,9 +434,9 @@ namespace eval punk::path {
set firstsegment [string range $str 0 $firstslash-1]
set firstsegment [string range $str 0 $firstslash-1]
}
}
if {[set firstc [string first : $firstsegment]] > 0} {
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
#count following / sequence
@ -466,7 +466,7 @@ namespace eval punk::path {
}
}
}
}
}
}
#assert first element of any return has been absolute or relative
#assert first element of any return has been absolute or relative
return relative
return relative
}
}
@ -489,7 +489,7 @@ namespace eval punk::path {
}
}
return $str
return $str
}
}
#purely string based - no reference to filesystem knowledge
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
#unix-style forward slash only
proc plainjoin {args} {
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set args [lmap a $args {string map "\\\\ /" $a}]
@ -499,12 +499,12 @@ namespace eval punk::path {
set out ""
set out ""
foreach a $args {
foreach a $args {
if {![string length $out]} {
if {![string length $out]} {
append out [plain $a]
append out [plain $a]
} else {
} else {
set a [plain $a]
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
set out [string range $out 0 end-1]
}
}
if {[string map {/ ""} $a] eq ""} {
if {[string map {/ ""} $a] eq ""} {
#all / segment
#all / segment
@ -512,16 +512,16 @@ namespace eval punk::path {
} else {
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
set a [string range $a 2 end]
}
}
if {[string index $out end] eq "/"} {
if {[string index $out end] eq "/"} {
append out $a
append out $a
} else {
} else {
append out / $a
append out / $a
}
}
}
}
}
}
}
}
return $out
return $out
}
}
proc plainjoin1 {args} {
proc plainjoin1 {args} {
if {[llength $args] == 1} {
if {[llength $args] == 1} {
@ -530,9 +530,9 @@ namespace eval punk::path {
set out [trim_final_slash [lindex $args 0]]
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
set a [trim_final_slash $a]
append out / $a
append out / $a
}
}
return $out
return $out
}
}
#intention?
#intention?
@ -554,13 +554,13 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[call [fun pathglob_as_re] [arg pathglob]]
#[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
#[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
#[para] ** matches any number of subdirectories.
#[para] ** matches any number of subdirectories.
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
@ -572,9 +572,9 @@ namespace eval punk::path {
}
}
switch -- $seg {
switch -- $seg {
* {lappend pats {[^/]*}}
* {lappend pats {[^/]*}}
** {lappend pats {.*}}
** {lappend pats {.*}}
default {
default {
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
**/_aside (exlude files where _aside is last segment)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
@values -min 0 -max -1 -optional 1 -type string
tailglobs -default * -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
within the directory tree being searched."
}
}
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[titledesc {Filesystem path utilities}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[moddesc {punk path filesystem utils}] [comment {-- Description at end of page heading --}]
#[require punk::path]
#[require punk::path]
#[description]
#[description]
#[keywords module path filesystem]
#[keywords module path filesystem]
@ -104,21 +104,21 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[subsection {Namespace punk::path}]
#[subsection {Namespace punk::path}]
#[para] Core API functions for punk::path
#[para] Core API functions for punk::path
#[list_begin definitions]
#[list_begin definitions]
# -- ---
# -- ---
#punk::path::normjoin
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
#Our default is to allow trackback to:
# <scheme>://<something>
# <scheme>://<something>
# <driveletter>:/
# <driveletter>:/
@ -128,7 +128,7 @@ namespace eval punk::path {
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#
#The caller should do the file/vfs operations to determine this - not us.
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
@ -148,9 +148,9 @@ namespace eval punk::path {
#known issues:
#known issues:
#1)
#1)
# normjoin d://a//b//c -> d://a/b/c
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
# won't fix?
#2)
#2)
@ -164,16 +164,16 @@ namespace eval punk::path {
# normjoin ///server/share -> ///server/share
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
# possibly won't fix - review
#4) inconsistency
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# but other dos device paths are maintained
# e.g //./c:/etc
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
#
# ================
# ================
#
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
@ -194,14 +194,14 @@ namespace eval punk::path {
/// {
/// {
#if this is effectively //$emptyservername/
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
#we can't transform to // or /
return ///
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
#todo - shortcircuit that here?
}
}
}
}
# ///
# ///
set doubleslash1_posn [string first // $path]
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
#on windows //host is of type volumerelative
@ -221,7 +221,7 @@ namespace eval punk::path {
}
}
# -- --- ---
# -- --- ---
set is_relpath 0
set is_relpath 0
#set path [string map [list \\ /] $path]
#set path [string map [list \\ /] $path]
set finalparts [list]
set finalparts [list]
@ -264,11 +264,11 @@ namespace eval punk::path {
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
#assert parts here has {} {} as first 2 entries
set rootindex 2
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
#if {"zipfs:" eq [lindex $parts 2]} {
@ -281,7 +281,7 @@ namespace eval punk::path {
#set parts [file split $path]
#set parts [file split $path]
set parts [::split $path /]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
if {[lindex $parts 1] eq ""} {
@ -295,9 +295,9 @@ namespace eval punk::path {
} elseif {[lindex $parts 0] ne ""} {
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
#relpath a/b/c
set parts [linsert $parts 0 .]
set parts [linsert $parts 0 .]
set rootindex 0
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
#also need to stop possible conversion to absolute path
set is_relpath 1
set is_relpath 1
}
}
}
}
@ -306,7 +306,7 @@ namespace eval punk::path {
#puts stderr "-->baseparts:$baseparts"
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
#
set finalparts [list [lindex $baseparts 0]]
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
if {$b ni {. ..}} {
@ -333,7 +333,7 @@ namespace eval punk::path {
lappend finalparts $p
lappend finalparts $p
}
}
}
}
incr i
incr i
}
}
} else {
} else {
foreach p [lrange $parts $rootindex+1 end] {
foreach p [lrange $parts $rootindex+1 end] {
@ -345,7 +345,7 @@ namespace eval punk::path {
switch -exact -- $p {
switch -exact -- $p {
. - "" {}
. - "" {}
.. {
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
}
default {
default {
lappend finalparts $p
lappend finalparts $p
@ -403,16 +403,16 @@ namespace eval punk::path {
}
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# COM1 = COM1:
# //./COM1 ?? review
# //./COM1 ?? review
proc pathtype {str} {
proc pathtype {str} {
@ -425,7 +425,7 @@ namespace eval punk::path {
return absolute
return absolute
}
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
set firstslash [string first / $str]
if {$firstslash == -1} {
if {$firstslash == -1} {
@ -434,9 +434,9 @@ namespace eval punk::path {
set firstsegment [string range $str 0 $firstslash-1]
set firstsegment [string range $str 0 $firstslash-1]
}
}
if {[set firstc [string first : $firstsegment]] > 0} {
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
#count following / sequence
@ -466,7 +466,7 @@ namespace eval punk::path {
}
}
}
}
}
}
#assert first element of any return has been absolute or relative
#assert first element of any return has been absolute or relative
return relative
return relative
}
}
@ -489,7 +489,7 @@ namespace eval punk::path {
}
}
return $str
return $str
}
}
#purely string based - no reference to filesystem knowledge
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
#unix-style forward slash only
proc plainjoin {args} {
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set args [lmap a $args {string map "\\\\ /" $a}]
@ -499,12 +499,12 @@ namespace eval punk::path {
set out ""
set out ""
foreach a $args {
foreach a $args {
if {![string length $out]} {
if {![string length $out]} {
append out [plain $a]
append out [plain $a]
} else {
} else {
set a [plain $a]
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
set out [string range $out 0 end-1]
}
}
if {[string map {/ ""} $a] eq ""} {
if {[string map {/ ""} $a] eq ""} {
#all / segment
#all / segment
@ -512,16 +512,16 @@ namespace eval punk::path {
} else {
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
set a [string range $a 2 end]
}
}
if {[string index $out end] eq "/"} {
if {[string index $out end] eq "/"} {
append out $a
append out $a
} else {
} else {
append out / $a
append out / $a
}
}
}
}
}
}
}
}
return $out
return $out
}
}
proc plainjoin1 {args} {
proc plainjoin1 {args} {
if {[llength $args] == 1} {
if {[llength $args] == 1} {
@ -530,9 +530,9 @@ namespace eval punk::path {
set out [trim_final_slash [lindex $args 0]]
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
set a [trim_final_slash $a]
append out / $a
append out / $a
}
}
return $out
return $out
}
}
#intention?
#intention?
@ -554,13 +554,13 @@ namespace eval punk::path {
#*** !doctools
#*** !doctools
#[call [fun pathglob_as_re] [arg pathglob]]
#[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
#[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
#[para] ** matches any number of subdirectories.
#[para] ** matches any number of subdirectories.
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**/*.txt will match any .txt files at any depth below /etc (except directly within /etc itself)
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] e.g /etc/**.txt will match any .txt files at any depth below /etc
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] any segment that does not contain ** must match exactly one segment in the path
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] e.g the glob /etc/*/*.doc - will match any .doc files that are exactly one tree level below /etc
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] The pathglob doesn't have to contain glob characters, in which case the returned regex will match the pathglob exactly as specified.
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#[para] Regular expression syntax is deliberateley not supported within the pathglob string so that supplied regex characters will be treated as literals
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
#todo - consider whether a way to escape the glob chars ? * is practical - to allow literals ? *
@ -572,9 +572,9 @@ namespace eval punk::path {
}
}
switch -- $seg {
switch -- $seg {
* {lappend pats {[^/]*}}
* {lappend pats {[^/]*}}
** {lappend pats {.*}}
** {lappend pats {.*}}
default {
default {
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
**/_aside (exlude files where _aside is last segment)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
@values -min 0 -max -1 -optional 1 -type string
tailglobs -default * -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
within the directory tree being searched."
}
}
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#todo - implement treefiles which acts like dirfiles but allows path globbing in the same way as punk::ns::ns/
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)
#then review if treefiles can replace dirfiles or if both should exist (dirfiles can have literal glob chars in path segments - but that is a rare usecase)