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"""Common code used by truth.""" 16*d6050574SRomain Jobredeaux 17*d6050574SRomain Jobredeauxload("@bazel_skylib//lib:types.bzl", "types") 18*d6050574SRomain Jobredeaux 19*d6050574SRomain Jobredeauxdef mkmethod(self, method): 20*d6050574SRomain Jobredeaux """Bind a struct as the first arg to a function. 21*d6050574SRomain Jobredeaux 22*d6050574SRomain Jobredeaux This is loosely equivalent to creating a bound method of a class. 23*d6050574SRomain Jobredeaux """ 24*d6050574SRomain Jobredeaux return lambda *args, **kwargs: method(self, *args, **kwargs) 25*d6050574SRomain Jobredeaux 26*d6050574SRomain Jobredeauxdef repr_with_type(value): 27*d6050574SRomain Jobredeaux return "<{} {}>".format(type(value), repr(value)) 28*d6050574SRomain Jobredeaux 29*d6050574SRomain Jobredeauxdef _informative_str(value): 30*d6050574SRomain Jobredeaux value_str = str(value) 31*d6050574SRomain Jobredeaux if not value_str: 32*d6050574SRomain Jobredeaux return "<empty string ∅>" 33*d6050574SRomain Jobredeaux elif "\n" in value_str: 34*d6050574SRomain Jobredeaux return '"""{}""" <sans triple-quotes; note newlines and whitespace>'.format(value_str) 35*d6050574SRomain Jobredeaux elif value_str != value_str.strip(): 36*d6050574SRomain Jobredeaux return '"{}" <sans quotes; note whitespace within>'.format(value_str) 37*d6050574SRomain Jobredeaux else: 38*d6050574SRomain Jobredeaux return value_str 39*d6050574SRomain Jobredeaux 40*d6050574SRomain Jobredeauxdef enumerate_list_as_lines(values, prefix = "", format_value = None): 41*d6050574SRomain Jobredeaux """Format a list of values in a human-friendly list. 42*d6050574SRomain Jobredeaux 43*d6050574SRomain Jobredeaux Args: 44*d6050574SRomain Jobredeaux values: ([`list`]) the values to display, one per line. 45*d6050574SRomain Jobredeaux prefix: ([`str`]) prefix to add before each line item. 46*d6050574SRomain Jobredeaux format_value: optional callable to convert each value to a string. 47*d6050574SRomain Jobredeaux If not specified, then an appropriate converter will be inferred 48*d6050574SRomain Jobredeaux based on the values. If specified, then the callable must accept 49*d6050574SRomain Jobredeaux 1 positional arg and return a string. 50*d6050574SRomain Jobredeaux 51*d6050574SRomain Jobredeaux Returns: 52*d6050574SRomain Jobredeaux [`str`]; the values formatted as a human-friendly list. 53*d6050574SRomain Jobredeaux """ 54*d6050574SRomain Jobredeaux if not values: 55*d6050574SRomain Jobredeaux return "{}<empty>".format(prefix) 56*d6050574SRomain Jobredeaux 57*d6050574SRomain Jobredeaux if format_value == None: 58*d6050574SRomain Jobredeaux format_value = guess_format_value(values) 59*d6050574SRomain Jobredeaux 60*d6050574SRomain Jobredeaux # Subtract 1 because we start at 0; i.e. length 10 prints 0 to 9 61*d6050574SRomain Jobredeaux max_i_width = len(str(len(values) - 1)) 62*d6050574SRomain Jobredeaux 63*d6050574SRomain Jobredeaux return "\n".join([ 64*d6050574SRomain Jobredeaux "{prefix}{ipad}{i}: {value}".format( 65*d6050574SRomain Jobredeaux prefix = prefix, 66*d6050574SRomain Jobredeaux ipad = " " * (max_i_width - len(str(i))), 67*d6050574SRomain Jobredeaux i = i, 68*d6050574SRomain Jobredeaux value = format_value(v), 69*d6050574SRomain Jobredeaux ) 70*d6050574SRomain Jobredeaux for i, v in enumerate(values) 71*d6050574SRomain Jobredeaux ]) 72*d6050574SRomain Jobredeaux 73*d6050574SRomain Jobredeauxdef guess_format_value(values): 74*d6050574SRomain Jobredeaux """Guess an appropriate human-friendly formatter to use with the value. 75*d6050574SRomain Jobredeaux 76*d6050574SRomain Jobredeaux Args: 77*d6050574SRomain Jobredeaux values: The object to pick a formatter for. 78*d6050574SRomain Jobredeaux 79*d6050574SRomain Jobredeaux Returns: 80*d6050574SRomain Jobredeaux callable that accepts the value. 81*d6050574SRomain Jobredeaux """ 82*d6050574SRomain Jobredeaux found_types = {} 83*d6050574SRomain Jobredeaux for value in values: 84*d6050574SRomain Jobredeaux found_types[type(value)] = None 85*d6050574SRomain Jobredeaux if len(found_types) > 1: 86*d6050574SRomain Jobredeaux return repr_with_type 87*d6050574SRomain Jobredeaux found_types = found_types.keys() 88*d6050574SRomain Jobredeaux if len(found_types) != 1: 89*d6050574SRomain Jobredeaux return repr_with_type 90*d6050574SRomain Jobredeaux elif found_types[0] in ("string", "File"): 91*d6050574SRomain Jobredeaux # For strings: omit the extra quotes and escaping. Just noise. 92*d6050574SRomain Jobredeaux # For Files: they include <TYPE path> already 93*d6050574SRomain Jobredeaux return _informative_str 94*d6050574SRomain Jobredeaux else: 95*d6050574SRomain Jobredeaux return repr_with_type 96*d6050574SRomain Jobredeaux 97*d6050574SRomain Jobredeauxdef maybe_sorted(container, allow_sorting = True): 98*d6050574SRomain Jobredeaux """Attempts to return the values of `container` in sorted order, if possible. 99*d6050574SRomain Jobredeaux 100*d6050574SRomain Jobredeaux Args: 101*d6050574SRomain Jobredeaux container: ([`list`] | (or other object convertible to list)) 102*d6050574SRomain Jobredeaux allow_sorting: ([`bool`]) whether to sort even if it can be sorted. This 103*d6050574SRomain Jobredeaux is primarily so that callers can avoid boilerplate when they have 104*d6050574SRomain Jobredeaux a "should it be sorted" arg, but also always convert to a list. 105*d6050574SRomain Jobredeaux 106*d6050574SRomain Jobredeaux Returns: 107*d6050574SRomain Jobredeaux A list, in sorted order if possible, otherwise in the original order. 108*d6050574SRomain Jobredeaux This *may* be the same object as given as input. 109*d6050574SRomain Jobredeaux """ 110*d6050574SRomain Jobredeaux container = to_list(container) 111*d6050574SRomain Jobredeaux if not allow_sorting: 112*d6050574SRomain Jobredeaux return container 113*d6050574SRomain Jobredeaux 114*d6050574SRomain Jobredeaux if all([_is_sortable(v) for v in container]): 115*d6050574SRomain Jobredeaux return sorted(container) 116*d6050574SRomain Jobredeaux else: 117*d6050574SRomain Jobredeaux return container 118*d6050574SRomain Jobredeaux 119*d6050574SRomain Jobredeauxdef _is_sortable(obj): 120*d6050574SRomain Jobredeaux return ( 121*d6050574SRomain Jobredeaux types.is_string(obj) or types.is_int(obj) or types.is_none(obj) or 122*d6050574SRomain Jobredeaux types.is_bool(obj) 123*d6050574SRomain Jobredeaux ) 124*d6050574SRomain Jobredeaux 125*d6050574SRomain Jobredeauxdef to_list(obj): 126*d6050574SRomain Jobredeaux """Attempt to convert the object to a list, else error. 127*d6050574SRomain Jobredeaux 128*d6050574SRomain Jobredeaux NOTE: This only supports objects that are typically understood as 129*d6050574SRomain Jobredeaux lists, not any iterable. Types like `dict` and `str` are iterable, 130*d6050574SRomain Jobredeaux but will be rejected. 131*d6050574SRomain Jobredeaux 132*d6050574SRomain Jobredeaux Args: 133*d6050574SRomain Jobredeaux obj: ([`list`] | [`depset`]) The object to convert to a list. 134*d6050574SRomain Jobredeaux 135*d6050574SRomain Jobredeaux Returns: 136*d6050574SRomain Jobredeaux [`list`] of the object 137*d6050574SRomain Jobredeaux """ 138*d6050574SRomain Jobredeaux if types.is_string(obj): 139*d6050574SRomain Jobredeaux fail("Cannot pass string to to_list(): {}".format(obj)) 140*d6050574SRomain Jobredeaux elif types.is_list(obj): 141*d6050574SRomain Jobredeaux return obj 142*d6050574SRomain Jobredeaux elif types.is_depset(obj): 143*d6050574SRomain Jobredeaux return obj.to_list() 144*d6050574SRomain Jobredeaux else: 145*d6050574SRomain Jobredeaux fail("Unable to convert to list: {}".format(repr_with_type(obj))) 146