Skip to content

Commit b73038c

Browse files
rickeylevBlaze Rules Copybara
authored andcommitted
Add matching.any, matching.all for composing matchers
I saw some recent convo about wanting "or" semantics for matchers and ran into a few such cases myself. `matching.any` and `matching.all` accept a list of matchers and use "or" and "and" semantics, respectively. Their names are modeled off the builtin functions, e.g. `any([f(v) for f in ..])` PiperOrigin-RevId: 684868010
1 parent 42f1ef0 commit b73038c

2 files changed

Lines changed: 87 additions & 2 deletions

File tree

lib/private/matching.bzl

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@
1414

1515
"""Implementation of matchers."""
1616

17+
def _match_all(*matchers):
18+
"""Match that all of multiple matchers match.
19+
20+
Args:
21+
*matchers: `list` of [`Matcher`]. If all match, then it is
22+
considered a match.
23+
24+
Returns:
25+
[`Matcher`] (see `_match_custom`)
26+
"""
27+
desc = " and ".join([str(m.desc) for m in matchers])
28+
29+
def _and(value):
30+
for m in matchers:
31+
if not m.match(value):
32+
return False
33+
return True
34+
35+
return struct(desc = desc, match = _and)
36+
1737
def _match_custom(desc, func):
1838
"""Wrap an arbitrary function up as a Matcher.
1939
@@ -116,6 +136,8 @@ def _match_is_in(values):
116136
This is equivalent to: `to_be_matched in values`. See `_match_contains`
117137
for the reversed operation.
118138
139+
See also: `matching.any` for matching amongst arbitrary other matchers.
140+
119141
Args:
120142
values: The collection that the value must be within.
121143
@@ -144,6 +166,29 @@ def _match_never(desc):
144166
match = lambda value: False,
145167
)
146168

169+
def _match_any(*matchers):
170+
"""Match that any of multiple matchers match.
171+
172+
See also: `is_in` for checking if a value is one amongst multiple
173+
values.
174+
175+
Args:
176+
*matchers: `list` of [`Matcher`]. If any match, then it is
177+
considered a match.
178+
179+
Returns:
180+
[`Matcher`] (see `_match_custom`)
181+
"""
182+
desc = " or ".join([str(m.desc) for m in matchers])
183+
184+
def _or(value):
185+
for m in matchers:
186+
if m.match(value):
187+
return True
188+
return False
189+
190+
return struct(desc = desc, match = _or)
191+
147192
def _match_contains(contained):
148193
"""Match that `contained` is within the to-be-matched value.
149194
@@ -220,18 +265,20 @@ def _is_matcher(obj):
220265
# For the definition of a `Matcher` object, see `_match_custom`.
221266
matching = struct(
222267
# keep sorted start
268+
all = _match_all,
269+
any = _match_any,
223270
contains = _match_contains,
224271
custom = _match_custom,
225272
equals_wrapper = _match_equals_wrapper,
226273
file_basename_contains = _match_file_basename_contains,
227274
file_basename_equals = _match_file_basename_equals,
228-
file_path_matches = _match_file_path_matches,
229275
file_extension_in = _match_file_extension_in,
276+
file_path_matches = _match_file_path_matches,
230277
is_in = _match_is_in,
278+
is_matcher = _is_matcher,
231279
never = _match_never,
232280
str_endswith = _match_str_endswith,
233281
str_matches = _match_str_matches,
234282
str_startswith = _match_str_startswith,
235-
is_matcher = _is_matcher,
236283
# keep sorted end
237284
)

tests/matching/matching_tests.bzl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,44 @@ def _verify_matcher(env, matcher, match_true, match_false):
2727
expr = "matcher.match(value)",
2828
).equals(False)
2929

30+
def _any_test(env):
31+
match_one = matching.equals_wrapper("one")
32+
match_two = matching.equals_wrapper("two")
33+
34+
_verify_matcher(
35+
env,
36+
matching.any(match_one, match_two),
37+
match_true = "one",
38+
match_false = "nope",
39+
)
40+
_verify_matcher(
41+
env,
42+
matching.any(match_one, match_two),
43+
match_true = "two",
44+
match_false = "nope",
45+
)
46+
47+
_tests.append(_any_test)
48+
49+
def _all_test(env):
50+
contains_x = matching.contains("x")
51+
contains_y = matching.contains("y")
52+
53+
_verify_matcher(
54+
env,
55+
matching.all(contains_x, contains_y),
56+
match_true = "xy",
57+
match_false = "xN",
58+
)
59+
_verify_matcher(
60+
env,
61+
matching.all(contains_x, contains_y),
62+
match_true = "yx",
63+
match_false = "yN",
64+
)
65+
66+
_tests.append(_all_test)
67+
3068
def _contains_test(env):
3169
_verify_matcher(
3270
env,

0 commit comments

Comments
 (0)