22import re
33from collections .abc import Iterable , Sequence
44from pathlib import PurePath
5- from typing import cast
5+ from typing import Literal , cast
66
77import mobase
88
99from .utils import is_directory
1010
1111_glob_pattern_matcher = re .compile (r"[*?\[\]]" )
1212
13- PatternPart = str | re .Pattern [str ]
13+ PatternPart = str | re .Pattern [str ] | Literal [ "*" , "**" ]
1414
1515
1616def glob_tree (
@@ -32,11 +32,11 @@ def glob_tree(
3232 if path and not path .endswith ("/" ):
3333 path += "/"
3434 if pattern .endswith (("/" , "\\ " )):
35- for res_path , entry in _glob_tree (file_tree , path , parts ):
35+ for res_path , entry in _glob_tree_parts (file_tree , parts , path ):
3636 if is_directory (entry ):
3737 yield res_path , entry
3838 else :
39- yield from _glob_tree (file_tree , path , parts )
39+ yield from _glob_tree_parts (file_tree , parts , path )
4040
4141
4242def parse_glob_pattern (pattern : str ) -> list [PatternPart ]:
@@ -53,7 +53,7 @@ def parse_glob_pattern(pattern: str) -> list[PatternPart]:
5353 res .append (part )
5454 elif part == "**" :
5555 if i + 1 < len (parts ) and parts [i + 1 ] == "**" :
56- raise ValueError ("Invalid pattern: '**/**' not supported!" )
56+ raise ValueError ("Invalid pattern: '**/**' is not supported!" )
5757 res .append (part )
5858 elif "**" in part :
5959 raise ValueError (
@@ -72,47 +72,65 @@ def has_wildcards(test_str: str):
7272 return _glob_pattern_matcher .search (test_str )
7373
7474
75- def _glob_tree (
75+ def _glob_tree_parts (
7676 file_tree : mobase .IFileTree ,
77- path : str ,
78- parts : Sequence [ PatternPart ] ,
79- double_star : bool = False ,
77+ pattern_parts : Sequence [ PatternPart ] ,
78+ path : str = "" ,
79+ recursive : bool = False ,
8080) -> Iterable [tuple [str , mobase .FileTreeEntry ]]:
81- # path ends with /
81+ """Glob the tree with a sequence of pattern parts, consisting of a regex pattern,
82+ `"*"`, `"**"` or a literal file tree entry name, as returned by `parse_glob_pattern`.
83+
84+ Args:
85+ file_tree: `IFileTree` test Args
86+ pattern_parts: List of the path parts: regex, `"*"`, `"**"` or name.
87+ path (optional): Path to the file tree,
88+ **must end with a slash ".../"**. Defaults to "".
89+ recursive (optional): Search for the pattern sequence recursive in the subtree, too.
90+ Same as preceding `pattern_parts` with "**". Defaults to False.
91+
92+ Yields:
93+ (path, entry)
94+
95+ See Also:
96+ parse_glob_pattern
97+ """
8298 simple_parts = 0
8399 re_pattern : re .Pattern [str ] | None = None
84100 doublestar_part = False
85- for part in parts :
86- if part == "*" :
87- break
88- if part == "**" :
89- doublestar_part = True
90- break
91- if isinstance (part , re .Pattern ):
92- re_pattern = part
93- break
94- simple_parts += 1
101+ for part in pattern_parts :
102+ match part :
103+ case "*" :
104+ break
105+ case "**" :
106+ doublestar_part = True
107+ break
108+ case re .Pattern ():
109+ re_pattern = part
110+ break
111+ case _:
112+ simple_parts += 1
95113 else :
96- # No glob patterns
97- sub_path = "/" .join (cast (Sequence [str ], parts ))
114+ # No wildcards
115+ sub_path = "/" .join (cast (Sequence [str ], pattern_parts ))
98116 if (entry := file_tree .find (sub_path )) is not None :
99117 yield path + sub_path , entry
100118 return
101119
102120 if simple_parts :
103121 # Get non pattern part directly
104- sub_path = "/" .join (cast (Sequence [str ], parts [:simple_parts ]))
122+ sub_path = "/" .join (cast (Sequence [str ], pattern_parts [:simple_parts ]))
105123 entry = file_tree .find (sub_path , mobase .FileTreeEntry .DIRECTORY )
106124 if entry is None or not is_directory (entry ):
107125 return
108126 file_tree = entry
109127 path = f"{ path } { sub_path } /"
110- rest = parts [simple_parts + 1 :]
128+ rest = pattern_parts [simple_parts + 1 :]
111129
112130 if doublestar_part :
113131 if rest :
114132 # **/...
115- yield from _glob_tree (file_tree , path , rest , True )
133+ yield from _glob_tree_parts (file_tree , rest , path , True )
116134 else :
117135 # .../**
118136 yield from all_tree_entries (file_tree , path )
@@ -123,12 +141,13 @@ def _glob_tree(
123141
124142 sub_path = path + name
125143 if not re_pattern or re_pattern .match (name ):
144+ # match or "*" part"
126145 if not rest :
127146 yield sub_path , entry
128147 elif is_directory (entry ):
129- yield from _glob_tree (entry , sub_path + "/" , rest , double_star )
130- if double_star and is_directory (entry ):
131- yield from _glob_tree (entry , sub_path + "/" , parts , double_star )
148+ yield from _glob_tree_parts (entry , rest , sub_path + "/" , recursive )
149+ if recursive and is_directory (entry ):
150+ yield from _glob_tree_parts (entry , pattern_parts , sub_path + "/" , recursive )
132151
133152
134153def all_tree_entries (
0 commit comments