1*d6050574SRomain Jobredeaux# Copyright 2023 The Bazel Authors. All rights reserved. 2*d6050574SRomain Jobredeaux# 3*d6050574SRomain Jobredeaux# Licensed under the Apache License, Version 2.0 (the "License"); 4*d6050574SRomain Jobredeaux# you may not use this file except in compliance with the License. 5*d6050574SRomain Jobredeaux# You may obtain a copy of the License at 6*d6050574SRomain Jobredeaux# 7*d6050574SRomain Jobredeaux# http://www.apache.org/licenses/LICENSE-2.0 8*d6050574SRomain Jobredeaux# 9*d6050574SRomain Jobredeaux# Unless required by applicable law or agreed to in writing, software 10*d6050574SRomain Jobredeaux# distributed under the License is distributed on an "AS IS" BASIS, 11*d6050574SRomain Jobredeaux# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*d6050574SRomain Jobredeaux# See the License for the specific language governing permissions and 13*d6050574SRomain Jobredeaux# limitations under the License. 14*d6050574SRomain Jobredeaux 15*d6050574SRomain Jobredeaux"""Implementation of matchers.""" 16*d6050574SRomain Jobredeaux 17*d6050574SRomain Jobredeauxdef _match_custom(desc, func): 18*d6050574SRomain Jobredeaux """Wrap an arbitrary function up as a Matcher. 19*d6050574SRomain Jobredeaux 20*d6050574SRomain Jobredeaux Method: Matcher.new 21*d6050574SRomain Jobredeaux 22*d6050574SRomain Jobredeaux `Matcher` struct attributes: 23*d6050574SRomain Jobredeaux 24*d6050574SRomain Jobredeaux * `desc`: ([`str`]) a human-friendly description 25*d6050574SRomain Jobredeaux * `match`: (callable) accepts 1 positional arg (the value to match) and 26*d6050574SRomain Jobredeaux returns [`bool`] (`True` if it matched, `False` if not). 27*d6050574SRomain Jobredeaux 28*d6050574SRomain Jobredeaux Args: 29*d6050574SRomain Jobredeaux desc: ([`str`]) a human-friendly string describing what is matched. 30*d6050574SRomain Jobredeaux func: (callable) accepts 1 positional arg (the value to match) and 31*d6050574SRomain Jobredeaux returns [`bool`] (`True` if it matched, `False` if not). 32*d6050574SRomain Jobredeaux 33*d6050574SRomain Jobredeaux Returns: 34*d6050574SRomain Jobredeaux [`Matcher`] (see above). 35*d6050574SRomain Jobredeaux """ 36*d6050574SRomain Jobredeaux return struct(desc = desc, match = func) 37*d6050574SRomain Jobredeaux 38*d6050574SRomain Jobredeauxdef _match_equals_wrapper(value): 39*d6050574SRomain Jobredeaux """Match that a value equals `value`, but use `value` as the `desc`. 40*d6050574SRomain Jobredeaux 41*d6050574SRomain Jobredeaux This is a helper so that simple equality comparisons can re-use predicate 42*d6050574SRomain Jobredeaux based APIs. 43*d6050574SRomain Jobredeaux 44*d6050574SRomain Jobredeaux Args: 45*d6050574SRomain Jobredeaux value: object, the value that must be equal to. 46*d6050574SRomain Jobredeaux 47*d6050574SRomain Jobredeaux Returns: 48*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom()`), whose description is `value`. 49*d6050574SRomain Jobredeaux """ 50*d6050574SRomain Jobredeaux return _match_custom(value, lambda other: other == value) 51*d6050574SRomain Jobredeaux 52*d6050574SRomain Jobredeauxdef _match_file_basename_contains(substr): 53*d6050574SRomain Jobredeaux """Match that a a `File.basename` string contains a substring. 54*d6050574SRomain Jobredeaux 55*d6050574SRomain Jobredeaux Args: 56*d6050574SRomain Jobredeaux substr: ([`str`]) the substring to match. 57*d6050574SRomain Jobredeaux 58*d6050574SRomain Jobredeaux Returns: 59*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom()`). 60*d6050574SRomain Jobredeaux """ 61*d6050574SRomain Jobredeaux return struct( 62*d6050574SRomain Jobredeaux desc = "<basename contains '{}'>".format(substr), 63*d6050574SRomain Jobredeaux match = lambda f: substr in f.basename, 64*d6050574SRomain Jobredeaux ) 65*d6050574SRomain Jobredeaux 66*d6050574SRomain Jobredeauxdef _match_file_path_matches(pattern): 67*d6050574SRomain Jobredeaux """Match that a `File.path` string matches a glob-style pattern. 68*d6050574SRomain Jobredeaux 69*d6050574SRomain Jobredeaux Args: 70*d6050574SRomain Jobredeaux pattern: ([`str`]) the pattern to match. "*" can be used to denote 71*d6050574SRomain Jobredeaux "match anything". 72*d6050574SRomain Jobredeaux 73*d6050574SRomain Jobredeaux Returns: 74*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom`). 75*d6050574SRomain Jobredeaux """ 76*d6050574SRomain Jobredeaux parts = pattern.split("*") 77*d6050574SRomain Jobredeaux return struct( 78*d6050574SRomain Jobredeaux desc = "<path matches '{}'>".format(pattern), 79*d6050574SRomain Jobredeaux match = lambda f: _match_parts_in_order(f.path, parts), 80*d6050574SRomain Jobredeaux ) 81*d6050574SRomain Jobredeaux 82*d6050574SRomain Jobredeauxdef _match_file_basename_equals(value): 83*d6050574SRomain Jobredeaux """Match that a `File.basename` string equals `value`. 84*d6050574SRomain Jobredeaux 85*d6050574SRomain Jobredeaux Args: 86*d6050574SRomain Jobredeaux value: ([`str`]) the basename to match. 87*d6050574SRomain Jobredeaux 88*d6050574SRomain Jobredeaux Returns: 89*d6050574SRomain Jobredeaux [`Matcher`] instance 90*d6050574SRomain Jobredeaux """ 91*d6050574SRomain Jobredeaux return struct( 92*d6050574SRomain Jobredeaux desc = "<file basename equals '{}'>".format(value), 93*d6050574SRomain Jobredeaux match = lambda f: f.basename == value, 94*d6050574SRomain Jobredeaux ) 95*d6050574SRomain Jobredeaux 96*d6050574SRomain Jobredeauxdef _match_file_extension_in(values): 97*d6050574SRomain Jobredeaux """Match that a `File.extension` string is any of `values`. 98*d6050574SRomain Jobredeaux 99*d6050574SRomain Jobredeaux See also: `file_path_matches` for matching extensions that 100*d6050574SRomain Jobredeaux have multiple parts, e.g. `*.tar.gz` or `*.so.*`. 101*d6050574SRomain Jobredeaux 102*d6050574SRomain Jobredeaux Args: 103*d6050574SRomain Jobredeaux values: ([`list`] of [`str`]) the extensions to match. 104*d6050574SRomain Jobredeaux 105*d6050574SRomain Jobredeaux Returns: 106*d6050574SRomain Jobredeaux [`Matcher`] instance 107*d6050574SRomain Jobredeaux """ 108*d6050574SRomain Jobredeaux return struct( 109*d6050574SRomain Jobredeaux desc = "<file extension is any of {}>".format(repr(values)), 110*d6050574SRomain Jobredeaux match = lambda f: f.extension in values, 111*d6050574SRomain Jobredeaux ) 112*d6050574SRomain Jobredeaux 113*d6050574SRomain Jobredeauxdef _match_is_in(values): 114*d6050574SRomain Jobredeaux """Match that the to-be-matched value is in a collection of other values. 115*d6050574SRomain Jobredeaux 116*d6050574SRomain Jobredeaux This is equivalent to: `to_be_matched in values`. See `_match_contains` 117*d6050574SRomain Jobredeaux for the reversed operation. 118*d6050574SRomain Jobredeaux 119*d6050574SRomain Jobredeaux Args: 120*d6050574SRomain Jobredeaux values: The collection that the value must be within. 121*d6050574SRomain Jobredeaux 122*d6050574SRomain Jobredeaux Returns: 123*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom()`). 124*d6050574SRomain Jobredeaux """ 125*d6050574SRomain Jobredeaux return struct( 126*d6050574SRomain Jobredeaux desc = "<is any of {}>".format(repr(values)), 127*d6050574SRomain Jobredeaux match = lambda v: v in values, 128*d6050574SRomain Jobredeaux ) 129*d6050574SRomain Jobredeaux 130*d6050574SRomain Jobredeauxdef _match_never(desc): 131*d6050574SRomain Jobredeaux """A matcher that never matches. 132*d6050574SRomain Jobredeaux 133*d6050574SRomain Jobredeaux This is mostly useful for testing, as it allows preventing any match 134*d6050574SRomain Jobredeaux while providing a custom description. 135*d6050574SRomain Jobredeaux 136*d6050574SRomain Jobredeaux Args: 137*d6050574SRomain Jobredeaux desc: ([`str`]) human-friendly string. 138*d6050574SRomain Jobredeaux 139*d6050574SRomain Jobredeaux Returns: 140*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom`). 141*d6050574SRomain Jobredeaux """ 142*d6050574SRomain Jobredeaux return struct( 143*d6050574SRomain Jobredeaux desc = desc, 144*d6050574SRomain Jobredeaux match = lambda value: False, 145*d6050574SRomain Jobredeaux ) 146*d6050574SRomain Jobredeaux 147*d6050574SRomain Jobredeauxdef _match_contains(contained): 148*d6050574SRomain Jobredeaux """Match that `contained` is within the to-be-matched value. 149*d6050574SRomain Jobredeaux 150*d6050574SRomain Jobredeaux This is equivalent to: `contained in to_be_matched`. See `_match_is_in` 151*d6050574SRomain Jobredeaux for the reversed operation. 152*d6050574SRomain Jobredeaux 153*d6050574SRomain Jobredeaux Args: 154*d6050574SRomain Jobredeaux contained: the value that to-be-matched value must contain. 155*d6050574SRomain Jobredeaux 156*d6050574SRomain Jobredeaux Returns: 157*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom`). 158*d6050574SRomain Jobredeaux """ 159*d6050574SRomain Jobredeaux return struct( 160*d6050574SRomain Jobredeaux desc = "<contains {}>".format(contained), 161*d6050574SRomain Jobredeaux match = lambda value: contained in value, 162*d6050574SRomain Jobredeaux ) 163*d6050574SRomain Jobredeaux 164*d6050574SRomain Jobredeauxdef _match_str_endswith(suffix): 165*d6050574SRomain Jobredeaux """Match that a string contains another string. 166*d6050574SRomain Jobredeaux 167*d6050574SRomain Jobredeaux Args: 168*d6050574SRomain Jobredeaux suffix: ([`str`]) the suffix that must be present 169*d6050574SRomain Jobredeaux 170*d6050574SRomain Jobredeaux Returns: 171*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom`). 172*d6050574SRomain Jobredeaux """ 173*d6050574SRomain Jobredeaux return struct( 174*d6050574SRomain Jobredeaux desc = "<endswith '{}'>".format(suffix), 175*d6050574SRomain Jobredeaux match = lambda value: value.endswith(suffix), 176*d6050574SRomain Jobredeaux ) 177*d6050574SRomain Jobredeaux 178*d6050574SRomain Jobredeauxdef _match_str_matches(pattern): 179*d6050574SRomain Jobredeaux """Match that a string matches a glob-style pattern. 180*d6050574SRomain Jobredeaux 181*d6050574SRomain Jobredeaux Args: 182*d6050574SRomain Jobredeaux pattern: ([`str`]) the pattern to match. `*` can be used to denote 183*d6050574SRomain Jobredeaux "match anything". There is an implicit `*` at the start and 184*d6050574SRomain Jobredeaux end of the pattern. 185*d6050574SRomain Jobredeaux 186*d6050574SRomain Jobredeaux Returns: 187*d6050574SRomain Jobredeaux [`Matcher`] object. 188*d6050574SRomain Jobredeaux """ 189*d6050574SRomain Jobredeaux parts = pattern.split("*") 190*d6050574SRomain Jobredeaux return struct( 191*d6050574SRomain Jobredeaux desc = "<matches '{}'>".format(pattern), 192*d6050574SRomain Jobredeaux match = lambda value: _match_parts_in_order(value, parts), 193*d6050574SRomain Jobredeaux ) 194*d6050574SRomain Jobredeaux 195*d6050574SRomain Jobredeauxdef _match_str_startswith(prefix): 196*d6050574SRomain Jobredeaux """Match that a string contains another string. 197*d6050574SRomain Jobredeaux 198*d6050574SRomain Jobredeaux Args: 199*d6050574SRomain Jobredeaux prefix: ([`str`]) the prefix that must be present 200*d6050574SRomain Jobredeaux 201*d6050574SRomain Jobredeaux Returns: 202*d6050574SRomain Jobredeaux [`Matcher`] (see `_match_custom`). 203*d6050574SRomain Jobredeaux """ 204*d6050574SRomain Jobredeaux return struct( 205*d6050574SRomain Jobredeaux desc = "<startswith '{}'>".format(prefix), 206*d6050574SRomain Jobredeaux match = lambda value: value.startswith(prefix), 207*d6050574SRomain Jobredeaux ) 208*d6050574SRomain Jobredeaux 209*d6050574SRomain Jobredeauxdef _match_parts_in_order(string, parts): 210*d6050574SRomain Jobredeaux start = 0 211*d6050574SRomain Jobredeaux for part in parts: 212*d6050574SRomain Jobredeaux start = string.find(part, start) 213*d6050574SRomain Jobredeaux if start == -1: 214*d6050574SRomain Jobredeaux return False 215*d6050574SRomain Jobredeaux return True 216*d6050574SRomain Jobredeaux 217*d6050574SRomain Jobredeauxdef _is_matcher(obj): 218*d6050574SRomain Jobredeaux return hasattr(obj, "desc") and hasattr(obj, "match") 219*d6050574SRomain Jobredeaux 220*d6050574SRomain Jobredeaux# For the definition of a `Matcher` object, see `_match_custom`. 221*d6050574SRomain Jobredeauxmatching = struct( 222*d6050574SRomain Jobredeaux # keep sorted start 223*d6050574SRomain Jobredeaux contains = _match_contains, 224*d6050574SRomain Jobredeaux custom = _match_custom, 225*d6050574SRomain Jobredeaux equals_wrapper = _match_equals_wrapper, 226*d6050574SRomain Jobredeaux file_basename_contains = _match_file_basename_contains, 227*d6050574SRomain Jobredeaux file_basename_equals = _match_file_basename_equals, 228*d6050574SRomain Jobredeaux file_path_matches = _match_file_path_matches, 229*d6050574SRomain Jobredeaux file_extension_in = _match_file_extension_in, 230*d6050574SRomain Jobredeaux is_in = _match_is_in, 231*d6050574SRomain Jobredeaux never = _match_never, 232*d6050574SRomain Jobredeaux str_endswith = _match_str_endswith, 233*d6050574SRomain Jobredeaux str_matches = _match_str_matches, 234*d6050574SRomain Jobredeaux str_startswith = _match_str_startswith, 235*d6050574SRomain Jobredeaux is_matcher = _is_matcher, 236*d6050574SRomain Jobredeaux # keep sorted end 237*d6050574SRomain Jobredeaux) 238