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"""# CollectionSubject""" 16*d6050574SRomain Jobredeaux 17*d6050574SRomain Jobredeauxload( 18*d6050574SRomain Jobredeaux ":check_util.bzl", 19*d6050574SRomain Jobredeaux "check_contains_at_least_predicates", 20*d6050574SRomain Jobredeaux "check_contains_exactly", 21*d6050574SRomain Jobredeaux "check_contains_exactly_predicates", 22*d6050574SRomain Jobredeaux "check_contains_none_of", 23*d6050574SRomain Jobredeaux "check_contains_predicate", 24*d6050574SRomain Jobredeaux "check_not_contains_predicate", 25*d6050574SRomain Jobredeaux) 26*d6050574SRomain Jobredeauxload( 27*d6050574SRomain Jobredeaux ":failure_messages.bzl", 28*d6050574SRomain Jobredeaux "format_actual_collection", 29*d6050574SRomain Jobredeaux "format_problem_expected_exactly", 30*d6050574SRomain Jobredeaux "format_problem_matched_out_of_order", 31*d6050574SRomain Jobredeaux "format_problem_missing_required_values", 32*d6050574SRomain Jobredeaux "format_problem_predicates_did_not_match", 33*d6050574SRomain Jobredeaux "format_problem_unexpected_values", 34*d6050574SRomain Jobredeaux) 35*d6050574SRomain Jobredeauxload(":int_subject.bzl", "IntSubject") 36*d6050574SRomain Jobredeauxload(":matching.bzl", "matching") 37*d6050574SRomain Jobredeauxload(":truth_common.bzl", "to_list") 38*d6050574SRomain Jobredeauxload(":util.bzl", "get_function_name") 39*d6050574SRomain Jobredeaux 40*d6050574SRomain Jobredeauxdef _identity(v): 41*d6050574SRomain Jobredeaux return v 42*d6050574SRomain Jobredeaux 43*d6050574SRomain Jobredeauxdef _always_true(v): 44*d6050574SRomain Jobredeaux _ = v # @unused 45*d6050574SRomain Jobredeaux return True 46*d6050574SRomain Jobredeaux 47*d6050574SRomain Jobredeauxdef _collection_subject_new( 48*d6050574SRomain Jobredeaux values, 49*d6050574SRomain Jobredeaux meta, 50*d6050574SRomain Jobredeaux container_name = "values", 51*d6050574SRomain Jobredeaux sortable = True, 52*d6050574SRomain Jobredeaux element_plural_name = "elements"): 53*d6050574SRomain Jobredeaux """Creates a "CollectionSubject" struct. 54*d6050574SRomain Jobredeaux 55*d6050574SRomain Jobredeaux Method: CollectionSubject.new 56*d6050574SRomain Jobredeaux 57*d6050574SRomain Jobredeaux Public Attributes: 58*d6050574SRomain Jobredeaux * `actual`: The wrapped collection. 59*d6050574SRomain Jobredeaux 60*d6050574SRomain Jobredeaux Args: 61*d6050574SRomain Jobredeaux values: ([`collection`]) the values to assert against. 62*d6050574SRomain Jobredeaux meta: ([`ExpectMeta`]) the metadata about the call chain. 63*d6050574SRomain Jobredeaux container_name: ([`str`]) conceptual name of the container. 64*d6050574SRomain Jobredeaux sortable: ([`bool`]) True if output should be sorted for display, False if not. 65*d6050574SRomain Jobredeaux element_plural_name: ([`str`]) the plural word for the values in the container. 66*d6050574SRomain Jobredeaux 67*d6050574SRomain Jobredeaux Returns: 68*d6050574SRomain Jobredeaux [`CollectionSubject`]. 69*d6050574SRomain Jobredeaux """ 70*d6050574SRomain Jobredeaux 71*d6050574SRomain Jobredeaux # buildifier: disable=uninitialized 72*d6050574SRomain Jobredeaux public = struct( 73*d6050574SRomain Jobredeaux # keep sorted start 74*d6050574SRomain Jobredeaux actual = values, 75*d6050574SRomain Jobredeaux contains = lambda *a, **k: _collection_subject_contains(self, *a, **k), 76*d6050574SRomain Jobredeaux contains_at_least = lambda *a, **k: _collection_subject_contains_at_least(self, *a, **k), 77*d6050574SRomain Jobredeaux contains_at_least_predicates = lambda *a, **k: _collection_subject_contains_at_least_predicates(self, *a, **k), 78*d6050574SRomain Jobredeaux contains_exactly = lambda *a, **k: _collection_subject_contains_exactly(self, *a, **k), 79*d6050574SRomain Jobredeaux contains_exactly_predicates = lambda *a, **k: _collection_subject_contains_exactly_predicates(self, *a, **k), 80*d6050574SRomain Jobredeaux contains_none_of = lambda *a, **k: _collection_subject_contains_none_of(self, *a, **k), 81*d6050574SRomain Jobredeaux contains_predicate = lambda *a, **k: _collection_subject_contains_predicate(self, *a, **k), 82*d6050574SRomain Jobredeaux has_size = lambda *a, **k: _collection_subject_has_size(self, *a, **k), 83*d6050574SRomain Jobredeaux not_contains = lambda *a, **k: _collection_subject_not_contains(self, *a, **k), 84*d6050574SRomain Jobredeaux not_contains_predicate = lambda *a, **k: _collection_subject_not_contains_predicate(self, *a, **k), 85*d6050574SRomain Jobredeaux offset = lambda *a, **k: _collection_subject_offset(self, *a, **k), 86*d6050574SRomain Jobredeaux transform = lambda *a, **k: _collection_subject_transform(self, *a, **k), 87*d6050574SRomain Jobredeaux # keep sorted end 88*d6050574SRomain Jobredeaux ) 89*d6050574SRomain Jobredeaux self = struct( 90*d6050574SRomain Jobredeaux actual = values, 91*d6050574SRomain Jobredeaux meta = meta, 92*d6050574SRomain Jobredeaux element_plural_name = element_plural_name, 93*d6050574SRomain Jobredeaux container_name = container_name, 94*d6050574SRomain Jobredeaux sortable = sortable, 95*d6050574SRomain Jobredeaux contains_predicate = public.contains_predicate, 96*d6050574SRomain Jobredeaux contains_at_least_predicates = public.contains_at_least_predicates, 97*d6050574SRomain Jobredeaux ) 98*d6050574SRomain Jobredeaux return public 99*d6050574SRomain Jobredeaux 100*d6050574SRomain Jobredeauxdef _collection_subject_has_size(self, expected): 101*d6050574SRomain Jobredeaux """Asserts that `expected` is the size of the collection. 102*d6050574SRomain Jobredeaux 103*d6050574SRomain Jobredeaux Method: CollectionSubject.has_size 104*d6050574SRomain Jobredeaux 105*d6050574SRomain Jobredeaux Args: 106*d6050574SRomain Jobredeaux self: implicitly added. 107*d6050574SRomain Jobredeaux expected: ([`int`]) the expected size of the collection. 108*d6050574SRomain Jobredeaux """ 109*d6050574SRomain Jobredeaux return IntSubject.new( 110*d6050574SRomain Jobredeaux len(self.actual), 111*d6050574SRomain Jobredeaux meta = self.meta.derive("size()"), 112*d6050574SRomain Jobredeaux ).equals(expected) 113*d6050574SRomain Jobredeaux 114*d6050574SRomain Jobredeauxdef _collection_subject_contains(self, expected): 115*d6050574SRomain Jobredeaux """Asserts that `expected` is within the collection. 116*d6050574SRomain Jobredeaux 117*d6050574SRomain Jobredeaux Method: CollectionSubject.contains 118*d6050574SRomain Jobredeaux 119*d6050574SRomain Jobredeaux Args: 120*d6050574SRomain Jobredeaux self: implicitly added. 121*d6050574SRomain Jobredeaux expected: ([`str`]) the value that must be present. 122*d6050574SRomain Jobredeaux """ 123*d6050574SRomain Jobredeaux matcher = matching.equals_wrapper(expected) 124*d6050574SRomain Jobredeaux return self.contains_predicate(matcher) 125*d6050574SRomain Jobredeaux 126*d6050574SRomain Jobredeauxdef _collection_subject_contains_exactly(self, expected): 127*d6050574SRomain Jobredeaux """Check that a collection contains exactly the given elements. 128*d6050574SRomain Jobredeaux 129*d6050574SRomain Jobredeaux Method: CollectionSubject.contains_exactly 130*d6050574SRomain Jobredeaux 131*d6050574SRomain Jobredeaux * Multiplicity is respected. 132*d6050574SRomain Jobredeaux * The collection must contain all the values, no more or less. 133*d6050574SRomain Jobredeaux * Checking that the order of matches is the same as the passed-in matchers 134*d6050574SRomain Jobredeaux order can be done by call `in_order()`. 135*d6050574SRomain Jobredeaux 136*d6050574SRomain Jobredeaux The collection must contain all the values and no more. Multiplicity of 137*d6050574SRomain Jobredeaux values is respected. Checking that the order of matches is the same as the 138*d6050574SRomain Jobredeaux passed-in matchers order can done by calling `in_order()`. 139*d6050574SRomain Jobredeaux 140*d6050574SRomain Jobredeaux Args: 141*d6050574SRomain Jobredeaux self: implicitly added. 142*d6050574SRomain Jobredeaux expected: ([`list`]) values that must exist. 143*d6050574SRomain Jobredeaux 144*d6050574SRomain Jobredeaux Returns: 145*d6050574SRomain Jobredeaux [`Ordered`] (see `_ordered_incorrectly_new`). 146*d6050574SRomain Jobredeaux """ 147*d6050574SRomain Jobredeaux expected = to_list(expected) 148*d6050574SRomain Jobredeaux return check_contains_exactly( 149*d6050574SRomain Jobredeaux actual_container = self.actual, 150*d6050574SRomain Jobredeaux expect_contains = expected, 151*d6050574SRomain Jobredeaux meta = self.meta, 152*d6050574SRomain Jobredeaux format_actual = lambda: format_actual_collection( 153*d6050574SRomain Jobredeaux self.actual, 154*d6050574SRomain Jobredeaux name = self.container_name, 155*d6050574SRomain Jobredeaux sort = False, # Don't sort; this might be rendered by the in_order() error. 156*d6050574SRomain Jobredeaux ), 157*d6050574SRomain Jobredeaux format_expected = lambda: format_problem_expected_exactly( 158*d6050574SRomain Jobredeaux expected, 159*d6050574SRomain Jobredeaux sort = False, # Don't sort; this might be rendered by the in_order() error. 160*d6050574SRomain Jobredeaux ), 161*d6050574SRomain Jobredeaux format_missing = lambda missing: format_problem_missing_required_values( 162*d6050574SRomain Jobredeaux missing, 163*d6050574SRomain Jobredeaux sort = self.sortable, 164*d6050574SRomain Jobredeaux ), 165*d6050574SRomain Jobredeaux format_unexpected = lambda unexpected: format_problem_unexpected_values( 166*d6050574SRomain Jobredeaux unexpected, 167*d6050574SRomain Jobredeaux sort = self.sortable, 168*d6050574SRomain Jobredeaux ), 169*d6050574SRomain Jobredeaux format_out_of_order = format_problem_matched_out_of_order, 170*d6050574SRomain Jobredeaux ) 171*d6050574SRomain Jobredeaux 172*d6050574SRomain Jobredeauxdef _collection_subject_contains_exactly_predicates(self, expected): 173*d6050574SRomain Jobredeaux """Check that the values correspond 1:1 to the predicates. 174*d6050574SRomain Jobredeaux 175*d6050574SRomain Jobredeaux Method: CollectionSubject.contains_exactly_predicates 176*d6050574SRomain Jobredeaux 177*d6050574SRomain Jobredeaux * There must be a 1:1 correspondence between the container values and the 178*d6050574SRomain Jobredeaux predicates. 179*d6050574SRomain Jobredeaux * Multiplicity is respected (i.e., if the same predicate occurs twice, then 180*d6050574SRomain Jobredeaux two distinct elements must match). 181*d6050574SRomain Jobredeaux * Matching occurs in first-seen order. That is, a predicate will "consume" 182*d6050574SRomain Jobredeaux the first value in `actual_container` it matches. 183*d6050574SRomain Jobredeaux * The collection must match all the predicates, no more or less. 184*d6050574SRomain Jobredeaux * Checking that the order of matches is the same as the passed-in matchers 185*d6050574SRomain Jobredeaux order can be done by call `in_order()`. 186*d6050574SRomain Jobredeaux 187*d6050574SRomain Jobredeaux Note that confusing results may occur if predicates with overlapping 188*d6050574SRomain Jobredeaux match conditions are used. For example, given: 189*d6050574SRomain Jobredeaux actual=["a", "ab", "abc"], 190*d6050574SRomain Jobredeaux predicates=[<contains a>, <contains b>, <equals a>] 191*d6050574SRomain Jobredeaux 192*d6050574SRomain Jobredeaux Then the result will be they aren't equal: the first two predicates 193*d6050574SRomain Jobredeaux consume "a" and "ab", leaving only "abc" for the <equals a> predicate 194*d6050574SRomain Jobredeaux to match against, which fails. 195*d6050574SRomain Jobredeaux 196*d6050574SRomain Jobredeaux Args: 197*d6050574SRomain Jobredeaux self: implicitly added. 198*d6050574SRomain Jobredeaux expected: ([`list`] of [`Matcher`]) that must match. 199*d6050574SRomain Jobredeaux 200*d6050574SRomain Jobredeaux Returns: 201*d6050574SRomain Jobredeaux [`Ordered`] (see `_ordered_incorrectly_new`). 202*d6050574SRomain Jobredeaux """ 203*d6050574SRomain Jobredeaux expected = to_list(expected) 204*d6050574SRomain Jobredeaux return check_contains_exactly_predicates( 205*d6050574SRomain Jobredeaux actual_container = self.actual, 206*d6050574SRomain Jobredeaux expect_contains = expected, 207*d6050574SRomain Jobredeaux meta = self.meta, 208*d6050574SRomain Jobredeaux format_actual = lambda: format_actual_collection( 209*d6050574SRomain Jobredeaux self.actual, 210*d6050574SRomain Jobredeaux name = self.container_name, 211*d6050574SRomain Jobredeaux sort = False, # Don't sort; this might be rendered by the in_order() error. 212*d6050574SRomain Jobredeaux ), 213*d6050574SRomain Jobredeaux format_expected = lambda: format_problem_expected_exactly( 214*d6050574SRomain Jobredeaux [e.desc for e in expected], 215*d6050574SRomain Jobredeaux sort = False, # Don't sort; this might be rendered by the in_order() error. 216*d6050574SRomain Jobredeaux ), 217*d6050574SRomain Jobredeaux format_missing = lambda missing: format_problem_missing_required_values( 218*d6050574SRomain Jobredeaux [m.desc for m in missing], 219*d6050574SRomain Jobredeaux sort = self.sortable, 220*d6050574SRomain Jobredeaux ), 221*d6050574SRomain Jobredeaux format_unexpected = lambda unexpected: format_problem_unexpected_values( 222*d6050574SRomain Jobredeaux unexpected, 223*d6050574SRomain Jobredeaux sort = self.sortable, 224*d6050574SRomain Jobredeaux ), 225*d6050574SRomain Jobredeaux format_out_of_order = format_problem_matched_out_of_order, 226*d6050574SRomain Jobredeaux ) 227*d6050574SRomain Jobredeaux 228*d6050574SRomain Jobredeauxdef _collection_subject_contains_none_of(self, values): 229*d6050574SRomain Jobredeaux """Asserts the collection contains none of `values`. 230*d6050574SRomain Jobredeaux 231*d6050574SRomain Jobredeaux Method: CollectionSubject.contains_none_of 232*d6050574SRomain Jobredeaux 233*d6050574SRomain Jobredeaux Args: 234*d6050574SRomain Jobredeaux self: implicitly added 235*d6050574SRomain Jobredeaux values: ([`collection`]) values of which none of are allowed to exist. 236*d6050574SRomain Jobredeaux """ 237*d6050574SRomain Jobredeaux check_contains_none_of( 238*d6050574SRomain Jobredeaux collection = self.actual, 239*d6050574SRomain Jobredeaux none_of = values, 240*d6050574SRomain Jobredeaux meta = self.meta, 241*d6050574SRomain Jobredeaux sort = self.sortable, 242*d6050574SRomain Jobredeaux ) 243*d6050574SRomain Jobredeaux 244*d6050574SRomain Jobredeauxdef _collection_subject_contains_predicate(self, matcher): 245*d6050574SRomain Jobredeaux """Asserts that `matcher` matches at least one value. 246*d6050574SRomain Jobredeaux 247*d6050574SRomain Jobredeaux Method: CollectionSubject.contains_predicate 248*d6050574SRomain Jobredeaux 249*d6050574SRomain Jobredeaux Args: 250*d6050574SRomain Jobredeaux self: implicitly added. 251*d6050574SRomain Jobredeaux matcher: ([`Matcher`]) (see `matchers` struct). 252*d6050574SRomain Jobredeaux """ 253*d6050574SRomain Jobredeaux check_contains_predicate( 254*d6050574SRomain Jobredeaux self.actual, 255*d6050574SRomain Jobredeaux matcher = matcher, 256*d6050574SRomain Jobredeaux format_problem = "expected to contain: {}".format(matcher.desc), 257*d6050574SRomain Jobredeaux format_actual = lambda: format_actual_collection( 258*d6050574SRomain Jobredeaux self.actual, 259*d6050574SRomain Jobredeaux name = self.container_name, 260*d6050574SRomain Jobredeaux sort = self.sortable, 261*d6050574SRomain Jobredeaux ), 262*d6050574SRomain Jobredeaux meta = self.meta, 263*d6050574SRomain Jobredeaux ) 264*d6050574SRomain Jobredeaux 265*d6050574SRomain Jobredeauxdef _collection_subject_contains_at_least(self, expect_contains): 266*d6050574SRomain Jobredeaux """Assert that the collection is a subset of the given predicates. 267*d6050574SRomain Jobredeaux 268*d6050574SRomain Jobredeaux Method: CollectionSubject.contains_at_least 269*d6050574SRomain Jobredeaux 270*d6050574SRomain Jobredeaux The collection must contain all the values. It can contain extra elements. 271*d6050574SRomain Jobredeaux The multiplicity of values is respected. Checking that the relative order 272*d6050574SRomain Jobredeaux of matches is the same as the passed-in expected values order can done by 273*d6050574SRomain Jobredeaux calling `in_order()`. 274*d6050574SRomain Jobredeaux 275*d6050574SRomain Jobredeaux Args: 276*d6050574SRomain Jobredeaux self: implicitly added. 277*d6050574SRomain Jobredeaux expect_contains: ([`list`]) values that must be in the collection. 278*d6050574SRomain Jobredeaux 279*d6050574SRomain Jobredeaux Returns: 280*d6050574SRomain Jobredeaux [`Ordered`] (see `_ordered_incorrectly_new`). 281*d6050574SRomain Jobredeaux """ 282*d6050574SRomain Jobredeaux matchers = [ 283*d6050574SRomain Jobredeaux matching.equals_wrapper(expected) 284*d6050574SRomain Jobredeaux for expected in to_list(expect_contains) 285*d6050574SRomain Jobredeaux ] 286*d6050574SRomain Jobredeaux return self.contains_at_least_predicates(matchers) 287*d6050574SRomain Jobredeaux 288*d6050574SRomain Jobredeauxdef _collection_subject_contains_at_least_predicates(self, matchers): 289*d6050574SRomain Jobredeaux """Assert that the collection is a subset of the given predicates. 290*d6050574SRomain Jobredeaux 291*d6050574SRomain Jobredeaux Method: CollectionSubject.contains_at_least_predicates 292*d6050574SRomain Jobredeaux 293*d6050574SRomain Jobredeaux The collection must match all the predicates. It can contain extra elements. 294*d6050574SRomain Jobredeaux The multiplicity of matchers is respected. Checking that the relative order 295*d6050574SRomain Jobredeaux of matches is the same as the passed-in matchers order can done by calling 296*d6050574SRomain Jobredeaux `in_order()`. 297*d6050574SRomain Jobredeaux 298*d6050574SRomain Jobredeaux Args: 299*d6050574SRomain Jobredeaux self: implicitly added. 300*d6050574SRomain Jobredeaux matchers: ([`list`] of [`Matcher`]) (see `matchers` struct). 301*d6050574SRomain Jobredeaux 302*d6050574SRomain Jobredeaux Returns: 303*d6050574SRomain Jobredeaux [`Ordered`] (see `_ordered_incorrectly_new`). 304*d6050574SRomain Jobredeaux """ 305*d6050574SRomain Jobredeaux ordered = check_contains_at_least_predicates( 306*d6050574SRomain Jobredeaux self.actual, 307*d6050574SRomain Jobredeaux matchers, 308*d6050574SRomain Jobredeaux format_missing = lambda missing: format_problem_predicates_did_not_match( 309*d6050574SRomain Jobredeaux missing, 310*d6050574SRomain Jobredeaux element_plural_name = self.element_plural_name, 311*d6050574SRomain Jobredeaux container_name = self.container_name, 312*d6050574SRomain Jobredeaux ), 313*d6050574SRomain Jobredeaux format_out_of_order = format_problem_matched_out_of_order, 314*d6050574SRomain Jobredeaux format_actual = lambda: format_actual_collection( 315*d6050574SRomain Jobredeaux self.actual, 316*d6050574SRomain Jobredeaux name = self.container_name, 317*d6050574SRomain Jobredeaux sort = self.sortable, 318*d6050574SRomain Jobredeaux ), 319*d6050574SRomain Jobredeaux meta = self.meta, 320*d6050574SRomain Jobredeaux ) 321*d6050574SRomain Jobredeaux return ordered 322*d6050574SRomain Jobredeaux 323*d6050574SRomain Jobredeauxdef _collection_subject_not_contains(self, value): 324*d6050574SRomain Jobredeaux check_not_contains_predicate( 325*d6050574SRomain Jobredeaux self.actual, 326*d6050574SRomain Jobredeaux matcher = matching.equals_wrapper(value), 327*d6050574SRomain Jobredeaux meta = self.meta, 328*d6050574SRomain Jobredeaux sort = self.sortable, 329*d6050574SRomain Jobredeaux ) 330*d6050574SRomain Jobredeaux 331*d6050574SRomain Jobredeauxdef _collection_subject_not_contains_predicate(self, matcher): 332*d6050574SRomain Jobredeaux """Asserts that `matcher` matches no values in the collection. 333*d6050574SRomain Jobredeaux 334*d6050574SRomain Jobredeaux Method: CollectionSubject.not_contains_predicate 335*d6050574SRomain Jobredeaux 336*d6050574SRomain Jobredeaux Args: 337*d6050574SRomain Jobredeaux self: implicitly added. 338*d6050574SRomain Jobredeaux matcher: [`Matcher`] object (see `matchers` struct). 339*d6050574SRomain Jobredeaux """ 340*d6050574SRomain Jobredeaux check_not_contains_predicate( 341*d6050574SRomain Jobredeaux self.actual, 342*d6050574SRomain Jobredeaux matcher = matcher, 343*d6050574SRomain Jobredeaux meta = self.meta, 344*d6050574SRomain Jobredeaux sort = self.sortable, 345*d6050574SRomain Jobredeaux ) 346*d6050574SRomain Jobredeaux 347*d6050574SRomain Jobredeauxdef _collection_subject_offset(self, offset, factory): 348*d6050574SRomain Jobredeaux """Fetches an element from the collection as a subject. 349*d6050574SRomain Jobredeaux 350*d6050574SRomain Jobredeaux Args: 351*d6050574SRomain Jobredeaux self: implicitly added. 352*d6050574SRomain Jobredeaux offset: ([`int`]) the offset to fetch 353*d6050574SRomain Jobredeaux factory: ([`callable`]). The factory function to use to create 354*d6050574SRomain Jobredeaux the subject for the offset's value. It must have the following 355*d6050574SRomain Jobredeaux signature: `def factory(value, *, meta)`. 356*d6050574SRomain Jobredeaux 357*d6050574SRomain Jobredeaux Returns: 358*d6050574SRomain Jobredeaux Object created by `factory`. 359*d6050574SRomain Jobredeaux """ 360*d6050574SRomain Jobredeaux value = self.actual[offset] 361*d6050574SRomain Jobredeaux return factory( 362*d6050574SRomain Jobredeaux value, 363*d6050574SRomain Jobredeaux meta = self.meta.derive("offset({})".format(offset)), 364*d6050574SRomain Jobredeaux ) 365*d6050574SRomain Jobredeaux 366*d6050574SRomain Jobredeauxdef _collection_subject_transform( 367*d6050574SRomain Jobredeaux self, 368*d6050574SRomain Jobredeaux desc = None, 369*d6050574SRomain Jobredeaux *, 370*d6050574SRomain Jobredeaux map_each = None, 371*d6050574SRomain Jobredeaux loop = None, 372*d6050574SRomain Jobredeaux filter = None): 373*d6050574SRomain Jobredeaux """Transforms a collections's value and returns another CollectionSubject. 374*d6050574SRomain Jobredeaux 375*d6050574SRomain Jobredeaux This is equivalent to applying a list comprehension over the collection values, 376*d6050574SRomain Jobredeaux but takes care of propagating context information and wrapping the value 377*d6050574SRomain Jobredeaux in a `CollectionSubject`. 378*d6050574SRomain Jobredeaux 379*d6050574SRomain Jobredeaux `transform(map_each=M, loop=L, filter=F)` is equivalent to 380*d6050574SRomain Jobredeaux `[M(v) for v in L(collection) if F(v)]`. 381*d6050574SRomain Jobredeaux 382*d6050574SRomain Jobredeaux Args: 383*d6050574SRomain Jobredeaux self: implicitly added. 384*d6050574SRomain Jobredeaux desc: (optional [`str`]) a human-friendly description of the transform 385*d6050574SRomain Jobredeaux for use in error messages. Required when a description can't be 386*d6050574SRomain Jobredeaux inferred from the other args. The description can be inferred if the 387*d6050574SRomain Jobredeaux filter arg is a named function (non-lambda) or Matcher object. 388*d6050574SRomain Jobredeaux map_each: (optional [`callable`]) function to transform an element in 389*d6050574SRomain Jobredeaux the collection. It takes one positional arg, the loop's 390*d6050574SRomain Jobredeaux current iteration value, and its return value will be the element's 391*d6050574SRomain Jobredeaux new value. If not specified, the values from the loop iteration are 392*d6050574SRomain Jobredeaux returned unchanged. 393*d6050574SRomain Jobredeaux loop: (optional [`callable`]) function to produce values from the 394*d6050574SRomain Jobredeaux original collection and whose values are iterated over. It takes one 395*d6050574SRomain Jobredeaux positional arg, which is the original collection. If not specified, 396*d6050574SRomain Jobredeaux the original collection values are iterated over. 397*d6050574SRomain Jobredeaux filter: (optional [`callable`]) function that decides what values are 398*d6050574SRomain Jobredeaux passed onto `map_each` for inclusion in the final result. It takes 399*d6050574SRomain Jobredeaux one positional arg, the value to match (which is the current 400*d6050574SRomain Jobredeaux iteration value before `map_each` is applied), and returns a bool 401*d6050574SRomain Jobredeaux (True if the value should be included in the result, False if it 402*d6050574SRomain Jobredeaux should be skipped). 403*d6050574SRomain Jobredeaux 404*d6050574SRomain Jobredeaux Returns: 405*d6050574SRomain Jobredeaux [`CollectionSubject`] of the transformed values. 406*d6050574SRomain Jobredeaux """ 407*d6050574SRomain Jobredeaux if not desc: 408*d6050574SRomain Jobredeaux if map_each or loop: 409*d6050574SRomain Jobredeaux fail("description required when map_each or loop used") 410*d6050574SRomain Jobredeaux 411*d6050574SRomain Jobredeaux if matching.is_matcher(filter): 412*d6050574SRomain Jobredeaux desc = "filter=" + filter.desc 413*d6050574SRomain Jobredeaux else: 414*d6050574SRomain Jobredeaux func_name = get_function_name(filter) 415*d6050574SRomain Jobredeaux if func_name == "lambda": 416*d6050574SRomain Jobredeaux fail("description required: description cannot be " + 417*d6050574SRomain Jobredeaux "inferred from lambdas. Explicitly specify the " + 418*d6050574SRomain Jobredeaux "description, use a named function for the filter, " + 419*d6050574SRomain Jobredeaux "or use a Matcher for the filter.") 420*d6050574SRomain Jobredeaux else: 421*d6050574SRomain Jobredeaux desc = "filter={}(...)".format(func_name) 422*d6050574SRomain Jobredeaux 423*d6050574SRomain Jobredeaux map_each = map_each or _identity 424*d6050574SRomain Jobredeaux loop = loop or _identity 425*d6050574SRomain Jobredeaux 426*d6050574SRomain Jobredeaux if filter: 427*d6050574SRomain Jobredeaux if matching.is_matcher(filter): 428*d6050574SRomain Jobredeaux filter_func = filter.match 429*d6050574SRomain Jobredeaux else: 430*d6050574SRomain Jobredeaux filter_func = filter 431*d6050574SRomain Jobredeaux else: 432*d6050574SRomain Jobredeaux filter_func = _always_true 433*d6050574SRomain Jobredeaux 434*d6050574SRomain Jobredeaux new_values = [map_each(v) for v in loop(self.actual) if filter_func(v)] 435*d6050574SRomain Jobredeaux 436*d6050574SRomain Jobredeaux return _collection_subject_new( 437*d6050574SRomain Jobredeaux new_values, 438*d6050574SRomain Jobredeaux meta = self.meta.derive( 439*d6050574SRomain Jobredeaux "transform()", 440*d6050574SRomain Jobredeaux details = ["transform: {}".format(desc)], 441*d6050574SRomain Jobredeaux ), 442*d6050574SRomain Jobredeaux container_name = self.container_name, 443*d6050574SRomain Jobredeaux sortable = self.sortable, 444*d6050574SRomain Jobredeaux element_plural_name = self.element_plural_name, 445*d6050574SRomain Jobredeaux ) 446*d6050574SRomain Jobredeaux 447*d6050574SRomain Jobredeaux# We use this name so it shows up nice in docs. 448*d6050574SRomain Jobredeaux# buildifier: disable=name-conventions 449*d6050574SRomain JobredeauxCollectionSubject = struct( 450*d6050574SRomain Jobredeaux # keep sorted start 451*d6050574SRomain Jobredeaux contains = _collection_subject_contains, 452*d6050574SRomain Jobredeaux contains_at_least = _collection_subject_contains_at_least, 453*d6050574SRomain Jobredeaux contains_at_least_predicates = _collection_subject_contains_at_least_predicates, 454*d6050574SRomain Jobredeaux contains_exactly = _collection_subject_contains_exactly, 455*d6050574SRomain Jobredeaux contains_exactly_predicates = _collection_subject_contains_exactly_predicates, 456*d6050574SRomain Jobredeaux contains_none_of = _collection_subject_contains_none_of, 457*d6050574SRomain Jobredeaux contains_predicate = _collection_subject_contains_predicate, 458*d6050574SRomain Jobredeaux has_size = _collection_subject_has_size, 459*d6050574SRomain Jobredeaux new = _collection_subject_new, 460*d6050574SRomain Jobredeaux not_contains_predicate = _collection_subject_not_contains_predicate, 461*d6050574SRomain Jobredeaux offset = _collection_subject_offset, 462*d6050574SRomain Jobredeaux transform = _collection_subject_transform, 463*d6050574SRomain Jobredeaux # keep sorted end 464*d6050574SRomain Jobredeaux) 465