xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/fnmatch.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""Filename matching with shell patterns.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard Workerfnmatch(FILENAME, PATTERN) matches according to the local convention.
4*cda5da8dSAndroid Build Coastguard Workerfnmatchcase(FILENAME, PATTERN) always takes case in account.
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard WorkerThe functions operate by translating the pattern into a regular
7*cda5da8dSAndroid Build Coastguard Workerexpression.  They cache the compiled regular expressions for speed.
8*cda5da8dSAndroid Build Coastguard Worker
9*cda5da8dSAndroid Build Coastguard WorkerThe function translate(PATTERN) returns a regular expression
10*cda5da8dSAndroid Build Coastguard Workercorresponding to PATTERN.  (It does not compile it.)
11*cda5da8dSAndroid Build Coastguard Worker"""
12*cda5da8dSAndroid Build Coastguard Workerimport os
13*cda5da8dSAndroid Build Coastguard Workerimport posixpath
14*cda5da8dSAndroid Build Coastguard Workerimport re
15*cda5da8dSAndroid Build Coastguard Workerimport functools
16*cda5da8dSAndroid Build Coastguard Worker
17*cda5da8dSAndroid Build Coastguard Worker__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
18*cda5da8dSAndroid Build Coastguard Worker
19*cda5da8dSAndroid Build Coastguard Workerdef fnmatch(name, pat):
20*cda5da8dSAndroid Build Coastguard Worker    """Test whether FILENAME matches PATTERN.
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard Worker    Patterns are Unix shell style:
23*cda5da8dSAndroid Build Coastguard Worker
24*cda5da8dSAndroid Build Coastguard Worker    *       matches everything
25*cda5da8dSAndroid Build Coastguard Worker    ?       matches any single character
26*cda5da8dSAndroid Build Coastguard Worker    [seq]   matches any character in seq
27*cda5da8dSAndroid Build Coastguard Worker    [!seq]  matches any char not in seq
28*cda5da8dSAndroid Build Coastguard Worker
29*cda5da8dSAndroid Build Coastguard Worker    An initial period in FILENAME is not special.
30*cda5da8dSAndroid Build Coastguard Worker    Both FILENAME and PATTERN are first case-normalized
31*cda5da8dSAndroid Build Coastguard Worker    if the operating system requires it.
32*cda5da8dSAndroid Build Coastguard Worker    If you don't want this, use fnmatchcase(FILENAME, PATTERN).
33*cda5da8dSAndroid Build Coastguard Worker    """
34*cda5da8dSAndroid Build Coastguard Worker    name = os.path.normcase(name)
35*cda5da8dSAndroid Build Coastguard Worker    pat = os.path.normcase(pat)
36*cda5da8dSAndroid Build Coastguard Worker    return fnmatchcase(name, pat)
37*cda5da8dSAndroid Build Coastguard Worker
38*cda5da8dSAndroid Build Coastguard Worker@functools.lru_cache(maxsize=32768, typed=True)
39*cda5da8dSAndroid Build Coastguard Workerdef _compile_pattern(pat):
40*cda5da8dSAndroid Build Coastguard Worker    if isinstance(pat, bytes):
41*cda5da8dSAndroid Build Coastguard Worker        pat_str = str(pat, 'ISO-8859-1')
42*cda5da8dSAndroid Build Coastguard Worker        res_str = translate(pat_str)
43*cda5da8dSAndroid Build Coastguard Worker        res = bytes(res_str, 'ISO-8859-1')
44*cda5da8dSAndroid Build Coastguard Worker    else:
45*cda5da8dSAndroid Build Coastguard Worker        res = translate(pat)
46*cda5da8dSAndroid Build Coastguard Worker    return re.compile(res).match
47*cda5da8dSAndroid Build Coastguard Worker
48*cda5da8dSAndroid Build Coastguard Workerdef filter(names, pat):
49*cda5da8dSAndroid Build Coastguard Worker    """Construct a list from those elements of the iterable NAMES that match PAT."""
50*cda5da8dSAndroid Build Coastguard Worker    result = []
51*cda5da8dSAndroid Build Coastguard Worker    pat = os.path.normcase(pat)
52*cda5da8dSAndroid Build Coastguard Worker    match = _compile_pattern(pat)
53*cda5da8dSAndroid Build Coastguard Worker    if os.path is posixpath:
54*cda5da8dSAndroid Build Coastguard Worker        # normcase on posix is NOP. Optimize it away from the loop.
55*cda5da8dSAndroid Build Coastguard Worker        for name in names:
56*cda5da8dSAndroid Build Coastguard Worker            if match(name):
57*cda5da8dSAndroid Build Coastguard Worker                result.append(name)
58*cda5da8dSAndroid Build Coastguard Worker    else:
59*cda5da8dSAndroid Build Coastguard Worker        for name in names:
60*cda5da8dSAndroid Build Coastguard Worker            if match(os.path.normcase(name)):
61*cda5da8dSAndroid Build Coastguard Worker                result.append(name)
62*cda5da8dSAndroid Build Coastguard Worker    return result
63*cda5da8dSAndroid Build Coastguard Worker
64*cda5da8dSAndroid Build Coastguard Workerdef fnmatchcase(name, pat):
65*cda5da8dSAndroid Build Coastguard Worker    """Test whether FILENAME matches PATTERN, including case.
66*cda5da8dSAndroid Build Coastguard Worker
67*cda5da8dSAndroid Build Coastguard Worker    This is a version of fnmatch() which doesn't case-normalize
68*cda5da8dSAndroid Build Coastguard Worker    its arguments.
69*cda5da8dSAndroid Build Coastguard Worker    """
70*cda5da8dSAndroid Build Coastguard Worker    match = _compile_pattern(pat)
71*cda5da8dSAndroid Build Coastguard Worker    return match(name) is not None
72*cda5da8dSAndroid Build Coastguard Worker
73*cda5da8dSAndroid Build Coastguard Worker
74*cda5da8dSAndroid Build Coastguard Workerdef translate(pat):
75*cda5da8dSAndroid Build Coastguard Worker    """Translate a shell PATTERN to a regular expression.
76*cda5da8dSAndroid Build Coastguard Worker
77*cda5da8dSAndroid Build Coastguard Worker    There is no way to quote meta-characters.
78*cda5da8dSAndroid Build Coastguard Worker    """
79*cda5da8dSAndroid Build Coastguard Worker
80*cda5da8dSAndroid Build Coastguard Worker    STAR = object()
81*cda5da8dSAndroid Build Coastguard Worker    res = []
82*cda5da8dSAndroid Build Coastguard Worker    add = res.append
83*cda5da8dSAndroid Build Coastguard Worker    i, n = 0, len(pat)
84*cda5da8dSAndroid Build Coastguard Worker    while i < n:
85*cda5da8dSAndroid Build Coastguard Worker        c = pat[i]
86*cda5da8dSAndroid Build Coastguard Worker        i = i+1
87*cda5da8dSAndroid Build Coastguard Worker        if c == '*':
88*cda5da8dSAndroid Build Coastguard Worker            # compress consecutive `*` into one
89*cda5da8dSAndroid Build Coastguard Worker            if (not res) or res[-1] is not STAR:
90*cda5da8dSAndroid Build Coastguard Worker                add(STAR)
91*cda5da8dSAndroid Build Coastguard Worker        elif c == '?':
92*cda5da8dSAndroid Build Coastguard Worker            add('.')
93*cda5da8dSAndroid Build Coastguard Worker        elif c == '[':
94*cda5da8dSAndroid Build Coastguard Worker            j = i
95*cda5da8dSAndroid Build Coastguard Worker            if j < n and pat[j] == '!':
96*cda5da8dSAndroid Build Coastguard Worker                j = j+1
97*cda5da8dSAndroid Build Coastguard Worker            if j < n and pat[j] == ']':
98*cda5da8dSAndroid Build Coastguard Worker                j = j+1
99*cda5da8dSAndroid Build Coastguard Worker            while j < n and pat[j] != ']':
100*cda5da8dSAndroid Build Coastguard Worker                j = j+1
101*cda5da8dSAndroid Build Coastguard Worker            if j >= n:
102*cda5da8dSAndroid Build Coastguard Worker                add('\\[')
103*cda5da8dSAndroid Build Coastguard Worker            else:
104*cda5da8dSAndroid Build Coastguard Worker                stuff = pat[i:j]
105*cda5da8dSAndroid Build Coastguard Worker                if '-' not in stuff:
106*cda5da8dSAndroid Build Coastguard Worker                    stuff = stuff.replace('\\', r'\\')
107*cda5da8dSAndroid Build Coastguard Worker                else:
108*cda5da8dSAndroid Build Coastguard Worker                    chunks = []
109*cda5da8dSAndroid Build Coastguard Worker                    k = i+2 if pat[i] == '!' else i+1
110*cda5da8dSAndroid Build Coastguard Worker                    while True:
111*cda5da8dSAndroid Build Coastguard Worker                        k = pat.find('-', k, j)
112*cda5da8dSAndroid Build Coastguard Worker                        if k < 0:
113*cda5da8dSAndroid Build Coastguard Worker                            break
114*cda5da8dSAndroid Build Coastguard Worker                        chunks.append(pat[i:k])
115*cda5da8dSAndroid Build Coastguard Worker                        i = k+1
116*cda5da8dSAndroid Build Coastguard Worker                        k = k+3
117*cda5da8dSAndroid Build Coastguard Worker                    chunk = pat[i:j]
118*cda5da8dSAndroid Build Coastguard Worker                    if chunk:
119*cda5da8dSAndroid Build Coastguard Worker                        chunks.append(chunk)
120*cda5da8dSAndroid Build Coastguard Worker                    else:
121*cda5da8dSAndroid Build Coastguard Worker                        chunks[-1] += '-'
122*cda5da8dSAndroid Build Coastguard Worker                    # Remove empty ranges -- invalid in RE.
123*cda5da8dSAndroid Build Coastguard Worker                    for k in range(len(chunks)-1, 0, -1):
124*cda5da8dSAndroid Build Coastguard Worker                        if chunks[k-1][-1] > chunks[k][0]:
125*cda5da8dSAndroid Build Coastguard Worker                            chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:]
126*cda5da8dSAndroid Build Coastguard Worker                            del chunks[k]
127*cda5da8dSAndroid Build Coastguard Worker                    # Escape backslashes and hyphens for set difference (--).
128*cda5da8dSAndroid Build Coastguard Worker                    # Hyphens that create ranges shouldn't be escaped.
129*cda5da8dSAndroid Build Coastguard Worker                    stuff = '-'.join(s.replace('\\', r'\\').replace('-', r'\-')
130*cda5da8dSAndroid Build Coastguard Worker                                     for s in chunks)
131*cda5da8dSAndroid Build Coastguard Worker                # Escape set operations (&&, ~~ and ||).
132*cda5da8dSAndroid Build Coastguard Worker                stuff = re.sub(r'([&~|])', r'\\\1', stuff)
133*cda5da8dSAndroid Build Coastguard Worker                i = j+1
134*cda5da8dSAndroid Build Coastguard Worker                if not stuff:
135*cda5da8dSAndroid Build Coastguard Worker                    # Empty range: never match.
136*cda5da8dSAndroid Build Coastguard Worker                    add('(?!)')
137*cda5da8dSAndroid Build Coastguard Worker                elif stuff == '!':
138*cda5da8dSAndroid Build Coastguard Worker                    # Negated empty range: match any character.
139*cda5da8dSAndroid Build Coastguard Worker                    add('.')
140*cda5da8dSAndroid Build Coastguard Worker                else:
141*cda5da8dSAndroid Build Coastguard Worker                    if stuff[0] == '!':
142*cda5da8dSAndroid Build Coastguard Worker                        stuff = '^' + stuff[1:]
143*cda5da8dSAndroid Build Coastguard Worker                    elif stuff[0] in ('^', '['):
144*cda5da8dSAndroid Build Coastguard Worker                        stuff = '\\' + stuff
145*cda5da8dSAndroid Build Coastguard Worker                    add(f'[{stuff}]')
146*cda5da8dSAndroid Build Coastguard Worker        else:
147*cda5da8dSAndroid Build Coastguard Worker            add(re.escape(c))
148*cda5da8dSAndroid Build Coastguard Worker    assert i == n
149*cda5da8dSAndroid Build Coastguard Worker
150*cda5da8dSAndroid Build Coastguard Worker    # Deal with STARs.
151*cda5da8dSAndroid Build Coastguard Worker    inp = res
152*cda5da8dSAndroid Build Coastguard Worker    res = []
153*cda5da8dSAndroid Build Coastguard Worker    add = res.append
154*cda5da8dSAndroid Build Coastguard Worker    i, n = 0, len(inp)
155*cda5da8dSAndroid Build Coastguard Worker    # Fixed pieces at the start?
156*cda5da8dSAndroid Build Coastguard Worker    while i < n and inp[i] is not STAR:
157*cda5da8dSAndroid Build Coastguard Worker        add(inp[i])
158*cda5da8dSAndroid Build Coastguard Worker        i += 1
159*cda5da8dSAndroid Build Coastguard Worker    # Now deal with STAR fixed STAR fixed ...
160*cda5da8dSAndroid Build Coastguard Worker    # For an interior `STAR fixed` pairing, we want to do a minimal
161*cda5da8dSAndroid Build Coastguard Worker    # .*? match followed by `fixed`, with no possibility of backtracking.
162*cda5da8dSAndroid Build Coastguard Worker    # Atomic groups ("(?>...)") allow us to spell that directly.
163*cda5da8dSAndroid Build Coastguard Worker    # Note: people rely on the undocumented ability to join multiple
164*cda5da8dSAndroid Build Coastguard Worker    # translate() results together via "|" to build large regexps matching
165*cda5da8dSAndroid Build Coastguard Worker    # "one of many" shell patterns.
166*cda5da8dSAndroid Build Coastguard Worker    while i < n:
167*cda5da8dSAndroid Build Coastguard Worker        assert inp[i] is STAR
168*cda5da8dSAndroid Build Coastguard Worker        i += 1
169*cda5da8dSAndroid Build Coastguard Worker        if i == n:
170*cda5da8dSAndroid Build Coastguard Worker            add(".*")
171*cda5da8dSAndroid Build Coastguard Worker            break
172*cda5da8dSAndroid Build Coastguard Worker        assert inp[i] is not STAR
173*cda5da8dSAndroid Build Coastguard Worker        fixed = []
174*cda5da8dSAndroid Build Coastguard Worker        while i < n and inp[i] is not STAR:
175*cda5da8dSAndroid Build Coastguard Worker            fixed.append(inp[i])
176*cda5da8dSAndroid Build Coastguard Worker            i += 1
177*cda5da8dSAndroid Build Coastguard Worker        fixed = "".join(fixed)
178*cda5da8dSAndroid Build Coastguard Worker        if i == n:
179*cda5da8dSAndroid Build Coastguard Worker            add(".*")
180*cda5da8dSAndroid Build Coastguard Worker            add(fixed)
181*cda5da8dSAndroid Build Coastguard Worker        else:
182*cda5da8dSAndroid Build Coastguard Worker            add(f"(?>.*?{fixed})")
183*cda5da8dSAndroid Build Coastguard Worker    assert i == n
184*cda5da8dSAndroid Build Coastguard Worker    res = "".join(res)
185*cda5da8dSAndroid Build Coastguard Worker    return fr'(?s:{res})\Z'
186