diff options
Diffstat (limited to 'lib/private/collection_subject.bzl')
-rw-r--r-- | lib/private/collection_subject.bzl | 124 |
1 files changed, 119 insertions, 5 deletions
diff --git a/lib/private/collection_subject.bzl b/lib/private/collection_subject.bzl index 8b093eb..6d72efe 100644 --- a/lib/private/collection_subject.bzl +++ b/lib/private/collection_subject.bzl @@ -35,6 +35,14 @@ load( load(":int_subject.bzl", "IntSubject") load(":matching.bzl", "matching") load(":truth_common.bzl", "to_list") +load(":util.bzl", "get_function_name") + +def _identity(v): + return v + +def _always_true(v): + _ = v # @unused + return True def _collection_subject_new( values, @@ -64,7 +72,6 @@ def _collection_subject_new( public = struct( # keep sorted start actual = values, - has_size = lambda *a, **k: _collection_subject_has_size(self, *a, **k), contains = lambda *a, **k: _collection_subject_contains(self, *a, **k), contains_at_least = lambda *a, **k: _collection_subject_contains_at_least(self, *a, **k), contains_at_least_predicates = lambda *a, **k: _collection_subject_contains_at_least_predicates(self, *a, **k), @@ -72,8 +79,11 @@ def _collection_subject_new( contains_exactly_predicates = lambda *a, **k: _collection_subject_contains_exactly_predicates(self, *a, **k), contains_none_of = lambda *a, **k: _collection_subject_contains_none_of(self, *a, **k), contains_predicate = lambda *a, **k: _collection_subject_contains_predicate(self, *a, **k), + has_size = lambda *a, **k: _collection_subject_has_size(self, *a, **k), not_contains = lambda *a, **k: _collection_subject_not_contains(self, *a, **k), not_contains_predicate = lambda *a, **k: _collection_subject_not_contains_predicate(self, *a, **k), + offset = lambda *a, **k: _collection_subject_offset(self, *a, **k), + transform = lambda *a, **k: _collection_subject_transform(self, *a, **k), # keep sorted end ) self = struct( @@ -334,17 +344,121 @@ def _collection_subject_not_contains_predicate(self, matcher): sort = self.sortable, ) +def _collection_subject_offset(self, offset, factory): + """Fetches an element from the collection as a subject. + + Args: + self: implicitly added. + offset: ([`int`]) the offset to fetch + factory: ([`callable`]). The factory function to use to create + the subject for the offset's value. It must have the following + signature: `def factory(value, *, meta)`. + + Returns: + Object created by `factory`. + """ + value = self.actual[offset] + return factory( + value, + meta = self.meta.derive("offset({})".format(offset)), + ) + +def _collection_subject_transform( + self, + desc = None, + *, + map_each = None, + loop = None, + filter = None): + """Transforms a collections's value and returns another CollectionSubject. + + This is equivalent to applying a list comprehension over the collection values, + but takes care of propagating context information and wrapping the value + in a `CollectionSubject`. + + `transform(map_each=M, loop=L, filter=F)` is equivalent to + `[M(v) for v in L(collection) if F(v)]`. + + Args: + self: implicitly added. + desc: (optional [`str`]) a human-friendly description of the transform + for use in error messages. Required when a description can't be + inferred from the other args. The description can be inferred if the + filter arg is a named function (non-lambda) or Matcher object. + map_each: (optional [`callable`]) function to transform an element in + the collection. It takes one positional arg, the loop's + current iteration value, and its return value will be the element's + new value. If not specified, the values from the loop iteration are + returned unchanged. + loop: (optional [`callable`]) function to produce values from the + original collection and whose values are iterated over. It takes one + positional arg, which is the original collection. If not specified, + the original collection values are iterated over. + filter: (optional [`callable`]) function that decides what values are + passed onto `map_each` for inclusion in the final result. It takes + one positional arg, the value to match (which is the current + iteration value before `map_each` is applied), and returns a bool + (True if the value should be included in the result, False if it + should be skipped). + + Returns: + [`CollectionSubject`] of the transformed values. + """ + if not desc: + if map_each or loop: + fail("description required when map_each or loop used") + + if matching.is_matcher(filter): + desc = "filter=" + filter.desc + else: + func_name = get_function_name(filter) + if func_name == "lambda": + fail("description required: description cannot be " + + "inferred from lambdas. Explicitly specify the " + + "description, use a named function for the filter, " + + "or use a Matcher for the filter.") + else: + desc = "filter={}(...)".format(func_name) + + map_each = map_each or _identity + loop = loop or _identity + + if filter: + if matching.is_matcher(filter): + filter_func = filter.match + else: + filter_func = filter + else: + filter_func = _always_true + + new_values = [map_each(v) for v in loop(self.actual) if filter_func(v)] + + return _collection_subject_new( + new_values, + meta = self.meta.derive( + "transform()", + details = ["transform: {}".format(desc)], + ), + container_name = self.container_name, + sortable = self.sortable, + element_plural_name = self.element_plural_name, + ) + # We use this name so it shows up nice in docs. # buildifier: disable=name-conventions CollectionSubject = struct( - new = _collection_subject_new, - has_size = _collection_subject_has_size, + # keep sorted start contains = _collection_subject_contains, + contains_at_least = _collection_subject_contains_at_least, + contains_at_least_predicates = _collection_subject_contains_at_least_predicates, contains_exactly = _collection_subject_contains_exactly, contains_exactly_predicates = _collection_subject_contains_exactly_predicates, contains_none_of = _collection_subject_contains_none_of, contains_predicate = _collection_subject_contains_predicate, - contains_at_least = _collection_subject_contains_at_least, - contains_at_least_predicates = _collection_subject_contains_at_least_predicates, + has_size = _collection_subject_has_size, + new = _collection_subject_new, not_contains_predicate = _collection_subject_not_contains_predicate, + offset = _collection_subject_offset, + transform = _collection_subject_transform, + # keep sorted end ) |