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