comparison cutils/util/fnmatch.py @ 302:bf88323d6bf7

treesum: Implement --exclude/--include. - Filtering - Document in output - Handle in the "info" command
author Franz Glasner <fzglas.hg@dom66.de>
date Wed, 05 Mar 2025 10:07:44 +0100
parents 1fc117f5f9a1
children dc1f08937621
comparison
equal deleted inserted replaced
301:d246b631b85a 302:bf88323d6bf7
8 """ 8 """
9 9
10 from __future__ import print_function, absolute_import 10 from __future__ import print_function, absolute_import
11 11
12 12
13 __all__ = [] 13 __all__ = ["FnMatcher"]
14
15
16 import re
17
18 from . import glob
14 19
15 20
16 HELP_DESCRIPTION = """\ 21 HELP_DESCRIPTION = """\
17 PATTERNs 22 PATTERNs
18 ======== 23 ========
20 glob: case-sensitive, anchored at the begin and end 25 glob: case-sensitive, anchored at the begin and end
21 iglob: case-insensitive variant of "glob" 26 iglob: case-insensitive variant of "glob"
22 re: regular expression 27 re: regular expression
23 path: plain text example (rooted), can be a file or a directory or a prefix 28 path: plain text example (rooted), can be a file or a directory or a prefix
24 thereof 29 thereof
25 filepath: exactly a single file, relative to the root of the tree 30 fullpath: exactly a single full path (file or directory), relative to the
31 root of the tree
26 32
27 """ 33 """
34
35
36 def glob_factory(pattern):
37
38 cpat = re.compile(
39 # automatically anchored
40 "\\A{}\\Z".format(glob.glob_to_regexp(pattern)),
41 re.DOTALL)
42
43 def _glob_matcher(s):
44 return cpat.search(s) is not None
45
46 return _glob_matcher
47
48
49 def iglob_factory(pattern):
50
51 cpat = re.compile(
52 # automatically anchored
53 "\\A{}\\Z".format(glob.glob_to_regexp(pattern)),
54 re.DOTALL | re.IGNORECASE)
55
56 def _iglob_matcher(s):
57 return cpat.search(s) is not None
58
59 return _iglob_matcher
60
61
62 def re_factory(pattern):
63
64 cpat = re.compile(pattern, re.DOTALL)
65
66 def _re_matcher(s):
67 return cpat.search(s) is not None
68
69 return _re_matcher
70
71
72 def path_factory(pattern):
73
74 def _path_matcher(s):
75 return s.startswith(pattern)
76
77 return _path_matcher
78
79
80 def fullpath_factory(pattern):
81
82 def _fullpath_matcher(s):
83 return s == pattern
84
85 return _fullpath_matcher
86
87
88 class FnMatcher(object):
89
90 _registry = {
91 "glob": glob_factory,
92 "iglob": iglob_factory,
93 "re": re_factory,
94 "path": path_factory,
95 "fullpath": fullpath_factory,
96 }
97
98 VISIT_DEFAULT = True # Overall default value for visiting
99
100 def __init__(self, matchers):
101 super(FnMatcher, self).__init__()
102 self._matchers = matchers
103
104 @classmethod
105 def build_from_commandline_patterns(klass, filter_definitions):
106 matchers = []
107 for action, kpattern in filter_definitions:
108 kind, sep, pattern = kpattern.partition(':')
109 if not sep:
110 # use the default
111 kind = "glob"
112 pattern = kpattern
113 factory = klass._registry.get(kind, None)
114 if not factory:
115 raise RuntimeError("unknown pattern kind: {}".format(kind))
116 matchers.append((action, kind, factory(pattern), pattern))
117
118 return klass(matchers)
119
120 def shall_visit(self, fn):
121 visit = self.VISIT_DEFAULT
122 for action, kind, matcher, orig_pattern in self._matchers:
123 res = matcher(fn)
124 if res:
125 if action == "include":
126 visit = True
127 elif action == "exclude":
128 visit = False
129 else:
130 raise RuntimeError("unknown action: {}".format(action))
131 return visit
132
133 def definitions(self):
134 for action, kind, matcher, orig_pattern in self._matchers:
135 yield (action, kind, orig_pattern)