3535load (":int_subject.bzl" , "IntSubject" )
3636load (":matching.bzl" , "matching" )
3737load (":truth_common.bzl" , "to_list" )
38+ load (":util.bzl" , "get_function_name" )
39+
40+ def _identity (v ):
41+ return v
42+
43+ def _always_true (v ):
44+ _ = v # @unused
45+ return True
3846
3947def _collection_subject_new (
4048 values ,
@@ -75,6 +83,7 @@ def _collection_subject_new(
7583 not_contains = lambda * a , ** k : _collection_subject_not_contains (self , * a , ** k ),
7684 not_contains_predicate = lambda * a , ** k : _collection_subject_not_contains_predicate (self , * a , ** k ),
7785 offset = lambda * a , ** k : _collection_subject_offset (self , * a , ** k ),
86+ transform = lambda * a , ** k : _collection_subject_transform (self , * a , ** k ),
7887 # keep sorted end
7988 )
8089 self = struct (
@@ -354,6 +363,85 @@ def _collection_subject_offset(self, offset, factory):
354363 meta = self .meta .derive ("offset({})" .format (offset )),
355364 )
356365
366+ def _collection_subject_transform (
367+ self ,
368+ desc = None ,
369+ * ,
370+ result = None ,
371+ loop = None ,
372+ filter = None ):
373+ """Transforms a collections's value and returns another CollectionSubject.
374+
375+ This is equivalent to applying a list comprehension over the collection values,
376+ but takes care of propagating context information and wrapping the value
377+ in a `CollectionSubject`.
378+
379+ `transform(result=R, loop=L, filter=F)` is equivalent to
380+ `[R(v) for v in L(collection) if F(v)]`.
381+
382+ Args:
383+ self: implicitly added.
384+ desc: (optional [`str`]) a human-friendly description of the transform
385+ for use in error messages. Required when a description can't be
386+ inferred from the other args. The description can be inferred if the
387+ filter arg is a named function (non-lambda) or Matcher object.
388+ result: (optional [`callable`]) function to transform an element in
389+ the collection. It takes one positional arg, which is the loop
390+ iteration value, and its return value will be the elements new
391+ value. If not specified, the values from the loop iteration are
392+ returned unchanged.
393+ loop: (optional [`callable`]) function to produce values from the
394+ original collection and whose values are iterated over. It takes one
395+ positional arg, which is the orignal collection. If not specified,
396+ the original collection values are iterated over.
397+ filter: (optional [`callable`]) function that decides what values are
398+ included into the result. It takes one positional arg, the value
399+ to match, and returns a bool (True if the value should be included
400+ in the result, False if it should be skipped).
401+
402+ Returns:
403+ [`CollectionSubject`] of the transformed values.
404+ """
405+ if not desc :
406+ if result or loop :
407+ fail ("description required when result or loop used" )
408+
409+ if matching .is_matcher (filter ):
410+ desc = "filter=" + filter .desc
411+ else :
412+ func_name = get_function_name (filter )
413+ if func_name == "lambda" :
414+ fail ("description required: description cannot be " +
415+ "inferred from lambdas. Explicitly specify the " +
416+ "description, use a named function for the filter, " +
417+ "or use a Matcher for the filter." )
418+ else :
419+ desc = "filter={}(...)" .format (func_name )
420+
421+ result = result or _identity
422+ loop = loop or _identity
423+
424+ if filter :
425+ if matching .is_matcher (filter ):
426+ filter_func = filter .match
427+ else :
428+ filter_func = filter
429+ else :
430+ filter_func = _always_true
431+
432+ new_values = [result (v ) for v in loop (self .actual ) if filter_func (v )]
433+
434+ return _collection_subject_new (
435+ new_values ,
436+ meta = self .meta .derive (
437+ "transform()" ,
438+ details = ["transform: {}" .format (desc )],
439+ ),
440+ container_name = self .container_name ,
441+ sortable = self .sortable ,
442+ element_plural_name = self .element_plural_name ,
443+ )
444+
357445# We use this name so it shows up nice in docs.
358446# buildifier: disable=name-conventions
359447CollectionSubject = struct (
@@ -369,5 +457,6 @@ CollectionSubject = struct(
369457 new = _collection_subject_new ,
370458 not_contains_predicate = _collection_subject_not_contains_predicate ,
371459 offset = _collection_subject_offset ,
460+ transform = _collection_subject_transform ,
372461 # keep sorted end
373462)
0 commit comments