Source code for flags

# -*- coding: utf-8 -*-
import collections
import functools
import pickle

from dictionaries import ReadonlyDictProxy

__all__ = ['Flags', 'FlagsMeta', 'FlagData', 'UNDEFINED', 'unique', 'unique_bits']


# version_info[0]: Increase in case of large milestones/releases.
# version_info[1]: Increase this and zero out version_info[2] if you have explicitly modified
#                  a previously existing behavior/interface.
#                  If the behavior of an existing feature changes as a result of a bugfix
#                  and the new (bugfixed) behavior is that meets the expectations of the
#                  previous interface documentation then you shouldn't increase this, in that
#                  case increase only version_info[2].
# version_info[2]: Increase in case of bugfixes. Also use this if you added new features
#                  without modifying the behavior of the previously existing ones.
version_info = (1, 1, 2)
__version__ = '.'.join(str(n) for n in version_info)
__author__ = 'István Pásztor'
__license__ = 'MIT'


def unique(flags_class):
    """ A decorator for flags classes to forbid flag aliases. """
    if not is_flags_class_final(flags_class):
        raise TypeError('unique check can be applied only to flags classes that have members')
    if not flags_class.__member_aliases__:
        return flags_class
    aliases = ', '.join('%s -> %s' % (alias, name) for alias, name in flags_class.__member_aliases__.items())
    raise ValueError('duplicate values found in %r: %s' % (flags_class, aliases))


def unique_bits(flags_class):
    """ A decorator for flags classes to forbid declaring flags with overlapping bits. """
    flags_class = unique(flags_class)
    other_bits = 0
    for name, member in flags_class.__members_without_aliases__.items():
        bits = int(member)
        if other_bits & bits:
            for other_name, other_member in flags_class.__members_without_aliases__.items():
                if int(other_member) & bits:
                    raise ValueError("%r: '%s' and '%s' have overlapping bits" % (flags_class, other_name, name))
        else:
            other_bits |= bits


def is_descriptor(obj):
    return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__')


class Const:
    def __init__(self, name):
        self.__name = name

    def __repr__(self):
        return self.__name


# "singleton" to be used as a const value with identity checks
UNDEFINED = Const('UNDEFINED')


def create_flags_subclass(base_enum_class, class_name, flags, *, mixins=(), module=None, qualname=None,
                          no_flags_name=UNDEFINED, all_flags_name=UNDEFINED):
    meta_class = type(base_enum_class)
    bases = tuple(mixins) + (base_enum_class,)
    class_dict = {'__members__': flags}
    if no_flags_name is not UNDEFINED:
        class_dict['__no_flags_name__'] = no_flags_name
    if all_flags_name is not UNDEFINED:
        class_dict['__all_flags_name__'] = all_flags_name
    flags_class = meta_class(class_name, bases, class_dict)

    # disabling on enabling pickle on the new class based on our module parameter
    if module is None:
        # Making the class unpicklable.
        def disabled_reduce_ex(self, proto):
            raise pickle.PicklingError("'%s' is unpicklable" % (type(self).__name__,))
        flags_class.__reduce_ex__ = disabled_reduce_ex

        # For pickle module==None means the __main__ module so let's change it to a non-existing name.
        # This will cause a failure while trying to pickle the class.
        module = '<unknown>'
    flags_class.__module__ = module

    if qualname is not None:
        flags_class.__qualname__ = qualname

    return flags_class


def process_inline_members_definition(members):
    """
    :param members: this can be any of the following:
    - a string containing a space and/or comma separated list of names: e.g.:
      "item1 item2 item3" OR "item1,item2,item3" OR "item1, item2, item3"
    - tuple/list/Set of strings (names)
    - Mapping of (name, data) pairs
    - any kind of iterable that yields (name, data) pairs
    :return: An iterable of (name, data) pairs.
    """
    if isinstance(members, str):
        members = ((name, UNDEFINED) for name in members.replace(',', ' ').split())
    elif isinstance(members, (tuple, list, collections.Set)):
        if members and isinstance(next(iter(members)), str):
            members = ((name, UNDEFINED) for name in members)
    elif isinstance(members, collections.Mapping):
        members = members.items()
    return members


def is_member_definition_class_attribute(name, value):
    """ Returns True if the given class attribute with the specified
    name and value should be treated as a flag member definition. """
    return not name.startswith('_') and not is_descriptor(value)


def extract_member_definitions_from_class_attributes(class_dict):
    members = [(name, value) for name, value in class_dict.items()
               if is_member_definition_class_attribute(name, value)]
    for name, _ in members:
        del class_dict[name]

    members.extend(process_inline_members_definition(class_dict.pop('__members__', ())))
    return members


class ReadonlyzerMixin:
    """ Makes instance attributes readonly after setting readonly=True. """
    __slots__ = ('__readonly',)

    def __init__(self, *args, readonly=False, **kwargs):
        # Calling super() before setting readonly.
        # This way super().__init__ can set attributes even if readonly==True
        super().__init__(*args, **kwargs)
        self.__readonly = readonly

    @property
    def readonly(self):
        try:
            return self.__readonly
        except AttributeError:
            return False

    @readonly.setter
    def readonly(self, value):
        self.__readonly = value

    def __setattr__(self, key, value):
        if self.readonly:
            raise AttributeError("Can't set attribute '%s' of readonly '%s' object" % (key, type(self).__name__))
        super().__setattr__(key, value)

    def __delattr__(self, key):
        if self.readonly:
            raise AttributeError("Can't delete attribute '%s' of readonly '%s' object" % (key, type(self).__name__))
        super().__delattr__(key)


class FlagProperties(ReadonlyzerMixin):
    __slots__ = ('name', 'data', 'bits', 'index', 'index_without_aliases')

    def __init__(self, *, name, bits, data=None, index=None, index_without_aliases=None):
        self.name = name
        self.data = data
        self.bits = bits
        self.index = index
        self.index_without_aliases = index_without_aliases
        super().__init__()


READONLY_PROTECTED_FLAGS_CLASS_ATTRIBUTES = frozenset([
    '__writable_protected_flags_class_attributes__', '__all_members__', '__members__', '__members_without_aliases__',
    '__member_aliases__', '__bits_to_properties__', '__bits_to_instance__', '__pickle_int_flags__',
])

# these attributes are writable when __writable_protected_flags_class_attributes__ is set to True on the class.
TEMPORARILY_WRITABLE_PROTECTED_FLAGS_CLASS_ATTRIBUTES = frozenset([
    '__all_bits__', '__no_flags__', '__all_flags__', '__no_flags_name__', '__all_flags_name__',
])

PROTECTED_FLAGS_CLASS_ATTRIBUTES = READONLY_PROTECTED_FLAGS_CLASS_ATTRIBUTES | \
                                   TEMPORARILY_WRITABLE_PROTECTED_FLAGS_CLASS_ATTRIBUTES


def is_valid_bits_value(bits):
    return isinstance(bits, int) and not isinstance(bits, bool)


def initialize_class_dict_and_create_flags_class(class_dict, class_name, create_flags_class):
    # all_members is used by __getattribute__ and __setattr__. It contains all items
    # from members and also the no_flags and all_flags special members if they are defined.
    all_members = collections.OrderedDict()
    members = collections.OrderedDict()
    members_without_aliases = collections.OrderedDict()
    bits_to_properties = collections.OrderedDict()
    bits_to_instance = collections.OrderedDict()
    member_aliases = collections.OrderedDict()
    class_dict['__all_members__'] = ReadonlyDictProxy(all_members)
    class_dict['__members__'] = ReadonlyDictProxy(members)
    class_dict['__members_without_aliases__'] = ReadonlyDictProxy(members_without_aliases)
    class_dict['__bits_to_properties__'] = ReadonlyDictProxy(bits_to_properties)
    class_dict['__bits_to_instance__'] = ReadonlyDictProxy(bits_to_instance)
    class_dict['__member_aliases__'] = ReadonlyDictProxy(member_aliases)

    flags_class = create_flags_class(class_dict)

    def instantiate_member(name, bits, special):
        if not isinstance(name, str):
            raise TypeError('Flag name should be an str but it is %r' % (name,))
        if not is_valid_bits_value(bits):
            raise TypeError("Bits for flag '%s' should be an int but it is %r" % (name, bits))
        if not special and bits == 0:
            raise ValueError("Flag '%s' has the invalid value of zero" % name)
        member = flags_class(bits)
        if int(member) != bits:
            raise RuntimeError("%s has altered the assigned bits of member '%s' from %r to %r" % (
                class_name, name, bits, int(member)))
        return member

    def register_member(member, name, bits, data, special):
        # special members (like no_flags, and all_flags) have no index
        # and they appear only in the __all_members__ collection.
        if all_members.setdefault(name, member) is not member:
            raise ValueError('Duplicate flag name: %r' % name)

        # It isn't a problem if an instance with the same bits already exists in bits_to_instance because
        # a member contains only the bits so our new member is equivalent with the replaced one.
        bits_to_instance[bits] = member

        if special:
            return

        members[name] = member
        properties = FlagProperties(name=name, bits=bits, data=data, index=len(members))
        properties_for_bits = bits_to_properties.setdefault(bits, properties)
        is_alias = properties_for_bits is not properties
        if is_alias:
            if data is not UNDEFINED:
                raise ValueError("You aren't allowed to associate data with alias '%s'" % name)
            member_aliases[name] = properties_for_bits.name
        else:
            properties.index_without_aliases = len(members_without_aliases)
            members_without_aliases[name] = member
        properties.readonly = True

    def instantiate_and_register_member(*, name, bits, data=None, special_member=False):
        member = instantiate_member(name, bits, special_member)
        register_member(member, name, bits, data, special_member)
        return member

    return flags_class, instantiate_and_register_member


def create_flags_class_with_members(class_name, class_dict, member_definitions, create_flags_class):
    class_dict['__writable_protected_flags_class_attributes__'] = True

    flags_class, instantiate_and_register_member = initialize_class_dict_and_create_flags_class(
        class_dict, class_name, create_flags_class)

    member_definitions = [(name, data) for name, data in member_definitions]
    member_definitions = flags_class.process_member_definitions(member_definitions)
    # member_definitions has to be an iterable of iterables yielding (name, bits, data)

    all_bits = 0
    for name, bits, data in member_definitions:
        instantiate_and_register_member(name=name, bits=bits, data=data)
        all_bits |= bits

    if len(flags_class) == 0:
        # In this case process_member_definitions() returned an empty iterable which isn't allowed.
        raise RuntimeError("%s.%s returned an empty iterable" %
                           (flags_class.__name__, flags_class.process_member_definitions.__name__))

    def instantiate_special_member(name, default_name, bits):
        name = default_name if name is None else name
        return instantiate_and_register_member(name=name, bits=bits, special_member=True)

    flags_class.__no_flags__ = instantiate_special_member(flags_class.__no_flags_name__, '__no_flags__', 0)
    flags_class.__all_flags__ = instantiate_special_member(flags_class.__all_flags_name__, '__all_flags__', all_bits)

    flags_class.__all_bits__ = all_bits

    del flags_class.__writable_protected_flags_class_attributes__
    return flags_class


class FlagData:
    pass


def is_flags_class_final(flags_class):
    return hasattr(flags_class, '__members__')


class FlagsMeta(type):
    def __new__(mcs, class_name, bases, class_dict):
        if '__slots__' in class_dict:
            raise RuntimeError("You aren't allowed to use __slots__ in your Flags subclasses")
        class_dict['__slots__'] = ()

        def create_flags_class(custom_class_dict=None):
            return super(FlagsMeta, mcs).__new__(mcs, class_name, bases, custom_class_dict or class_dict)

        if Flags is None:
            # This __new__ call is creating the Flags class of this module.
            return create_flags_class()

        flags_bases = [base for base in bases if issubclass(base, Flags)]
        for base in flags_bases:
            # pylint: disable=protected-access
            if is_flags_class_final(base):
                raise RuntimeError("You can't subclass '%s' because it has already defined flag members" %
                                   (base.__name__,))

        member_definitions = extract_member_definitions_from_class_attributes(class_dict)
        if not member_definitions:
            return create_flags_class()
        return create_flags_class_with_members(class_name, class_dict, member_definitions, create_flags_class)

    def __call__(cls, *args, **kwargs):
        if kwargs or len(args) >= 2:
            # The Flags class or one of its subclasses was "called" as a
            # utility function to create a subclass of the called class.
            return create_flags_subclass(cls, *args, **kwargs)

        # We have zero or one positional argument and we have to create and/or return an exact instance of cls.
        # 1. Zero argument means we have to return a zero flag.
        # 2. A single positional argument can be one of the following cases:
        #    1. An object whose class is exactly cls.
        #    2. An str object that comes from Flags.__str__() or Flags.to_simple_str()
        #    3. An int object that specifies the bits of the Flags instance to be created.

        if not is_flags_class_final(cls):
            raise RuntimeError("Instantiation of abstract flags class '%s.%s' isn't allowed." % (
                cls.__module__, cls.__name__))

        if not args:
            # case 1 - zero positional arguments, we have to return a zero flag
            return cls.__no_flags__

        value = args[0]

        if type(value) is cls:
            # case 2.1
            return value

        if isinstance(value, str):
            # case 2.2
            bits = cls.bits_from_str(value)
        elif is_valid_bits_value(value):
            # case 2.3
            bits = cls.__all_bits__ & value
        else:
            raise TypeError("Can't instantiate flags class '%s' from value %r" % (cls.__name__, value))

        instance = cls.__bits_to_instance__.get(bits)
        if instance:
            return instance
        return super().__call__(bits)

    @classmethod
    def __prepare__(cls, class_name, bases):
        return collections.OrderedDict()

    def __delattr__(cls, name):
        if (name in PROTECTED_FLAGS_CLASS_ATTRIBUTES and name != '__writable_protected_flags_class_attributes__') or\
                (name in getattr(cls, '__all_members__', {})):
            raise AttributeError("Can't delete protected attribute '%s'" % name)
        super().__delattr__(name)

    def __setattr__(cls, name, value):
        if name in PROTECTED_FLAGS_CLASS_ATTRIBUTES:
            if name in READONLY_PROTECTED_FLAGS_CLASS_ATTRIBUTES or\
                    not getattr(cls, '__writable_protected_flags_class_attributes__', False):
                raise AttributeError("Can't assign protected attribute '%s'" % name)
        elif name in getattr(cls, '__all_members__', {}):
            raise AttributeError("Can't assign protected attribute '%s'" % name)
        super().__setattr__(name, value)

    def __getattr__(cls, name):
        try:
            return super().__getattribute__('__all_members__')[name]
        except KeyError:
            raise AttributeError(name)

    def __getitem__(cls, name):
        return cls.__all_members__[name]

    def __iter__(cls):
        return iter(cls.__members_without_aliases__.values())

    def __reversed__(cls):
        return reversed(list(cls.__members_without_aliases__.values()))

    def __bool__(cls):
        return True

    def __len__(cls):
        members = getattr(cls, '__members_without_aliases__', ())
        return len(members)

    def flag_attribute_value_to_bits_and_data(cls, name, value):
        if value is UNDEFINED:
            return UNDEFINED, UNDEFINED
        elif isinstance(value, FlagData):
            return UNDEFINED, value
        elif is_valid_bits_value(value):
            return value, UNDEFINED
        elif isinstance(value, collections.Iterable):
            arr = tuple(value)
            if len(arr) == 0:
                return UNDEFINED, UNDEFINED
            if len(arr) == 1:
                return UNDEFINED, arr[0]
            if len(arr) == 2:
                return arr
            raise ValueError("Iterable is expected to have at most 2 items instead of %s "
                             "for flag '%s', iterable: %r" % (len(arr), name, value))
        raise TypeError("Expected an int or an iterable of at most 2 items "
                        "for flag '%s', received %r" % (name, value))

    def process_member_definitions(cls, member_definitions):
        """
        The incoming member_definitions contains the class attributes (with their values) that are
        used to define the flag members. This method can do anything to the incoming list and has to
        return a final set of flag definitions that assigns bits to the members. The returned member
        definitions can be completely different or unrelated to the incoming ones.
        :param member_definitions: A list of (name, data) tuples.
        :return: An iterable of iterables yielding 3 items: name, bits, data
        """
        members = []
        auto_flags = []
        all_bits = 0
        for name, data in member_definitions:
            bits, data = cls.flag_attribute_value_to_bits_and_data(name, data)
            if bits is UNDEFINED:
                auto_flags.append(len(members))
                members.append((name, data))
            elif is_valid_bits_value(bits):
                all_bits |= bits
                members.append((name, bits, data))
            else:
                raise TypeError("Expected an int value as the bits of flag '%s', received %r" % (name, bits))

        # auto-assigning unused bits to members without custom defined bits
        bit = 1
        for index in auto_flags:
            while bit & all_bits:
                bit <<= 1
            name, data = members[index]
            members[index] = name, bit, data
            bit <<= 1

        return members

    def __repr__(cls):
        return "<flags %s>" % cls.__name__

    __no_flags_name__ = 'no_flags'
    __all_flags_name__ = 'all_flags'
    __dotted_single_flag_str__ = True
    __pickle_int_flags__ = False
    __all_bits__ = -1

    # TODO: utility method to fill the flag members to a namespace, and another utility that can fill
    # them to a module (a specific case of namespaces)


def operator_requires_type_identity(wrapped):
    @functools.wraps(wrapped)
    def wrapper(self, other):
        if type(other) is not type(self):
            return NotImplemented
        return wrapped(self, other)
    return wrapper


class FlagsArithmeticMixin:
    __slots__ = ('__bits',)

    def __new__(cls, bits):
        instance = super().__new__(cls)
        # pylint: disable=protected-access
        instance.__bits = bits & cls.__all_bits__
        return instance

    def __int__(self):
        return self.__bits

    def __bool__(self):
        return self.__bits != 0

    def __contains__(self, item):
        if type(item) is not type(self):
            return False
        # this logic is equivalent to that of __ge__(self, item) and __le__(item, self)
        # pylint: disable=protected-access
        return item.__bits == (self.__bits & item.__bits)

    def is_disjoint(self, *flags_instances):
        for flags in flags_instances:
            if self & flags:
                return False
        return True

    def __create_flags_instance(self, bits):
        # optimization, exploiting immutability
        if bits == self.__bits:
            return self
        return type(self)(bits)

    @operator_requires_type_identity
    def __or__(self, other):
        # pylint: disable=protected-access
        return self.__create_flags_instance(self.__bits | other.__bits)

    @operator_requires_type_identity
    def __xor__(self, other):
        # pylint: disable=protected-access
        return self.__create_flags_instance(self.__bits ^ other.__bits)

    @operator_requires_type_identity
    def __and__(self, other):
        # pylint: disable=protected-access
        return self.__create_flags_instance(self.__bits & other.__bits)

    @operator_requires_type_identity
    def __sub__(self, other):
        # pylint: disable=protected-access
        bits = self.__bits ^ (self.__bits & other.__bits)
        return self.__create_flags_instance(bits)

    @operator_requires_type_identity
    def __eq__(self, other):
        # pylint: disable=protected-access
        return self.__bits == other.__bits

    @operator_requires_type_identity
    def __ne__(self, other):
        # pylint: disable=protected-access
        return self.__bits != other.__bits

    @operator_requires_type_identity
    def __ge__(self, other):
        # pylint: disable=protected-access
        return other.__bits == (self.__bits & other.__bits)

    @operator_requires_type_identity
    def __gt__(self, other):
        # pylint: disable=protected-access
        return (self.__bits != other.__bits) and (other.__bits == (self.__bits & other.__bits))

    @operator_requires_type_identity
    def __le__(self, other):
        # pylint: disable=protected-access
        return self.__bits == (self.__bits & other.__bits)

    @operator_requires_type_identity
    def __lt__(self, other):
        # pylint: disable=protected-access
        return (self.__bits != other.__bits) and (self.__bits == (self.__bits & other.__bits))

    def __invert__(self):
        return self.__create_flags_instance(self.__bits ^ type(self).__all_bits__)


# This is used by FlagsMeta to detect whether the flags class currently being created is Flags.
Flags = None


class Flags(FlagsArithmeticMixin, metaclass=FlagsMeta):
    @property
    def is_member(self):
        """ `flags.is_member` is a shorthand for `flags.properties is not None`.
        If this property is False then this Flags instance has either zero bits or holds a combination
        of flag member bits.
        If this property is True then the bits of this Flags instance match exactly the bits associated
        with one of the members. This however doesn't necessarily mean that this flag instance isn't a
        combination of several flags because the bits of a member can be the subset of another member.
        For example if member0_bits=0x1 and member1_bits=0x3 then the bits of member0 are a subset of
        the bits of member1. If a flag instance holds the bits of member1 then Flags.is_member returns
        True and Flags.properties returns the properties of member1 but __len__() returns 2 and
        __iter__() yields both member0 and member1.
        """
        return type(self).__bits_to_properties__.get(int(self)) is not None

    @property
    def properties(self):
        """
        :return: Returns None if this flag isn't an exact member of a flags class but a combination of flags,
        returns an object holding the properties (e.g.: name, data, index, ...) of the flag otherwise.
        We don't store flag properties directly in Flags instances because this way Flags instances that are
        the (temporary) result of flags arithmetic don't have to maintain these fields and it also has some
        benefits regarding memory usage. """
        return type(self).__bits_to_properties__.get(int(self))

    @property
    def name(self):
        properties = self.properties
        return self.properties.name if properties else None

    @property
    def data(self):
        properties = self.properties
        return self.properties.data if properties else UNDEFINED

    def __getattr__(self, name):
        try:
            member = type(self).__members__[name]
        except KeyError:
            raise AttributeError(name)
        return member in self

    def __iter__(self):
        members = type(self).__members_without_aliases__.values()
        return (member for member in members if member in self)

    def __reversed__(self):
        members = reversed(list(type(self).__members_without_aliases__.values()))
        return (member for member in members if member in self)

    def __len__(self):
        return sum(1 for _ in self)

    def __hash__(self):
        return int(self) ^ hash(type(self))

    def __reduce_ex__(self, proto):
        value = int(self) if type(self).__pickle_int_flags__ else self.to_simple_str()
        return type(self), (value,)

    def __str__(self):
        # Warning: The output of this method has to be a string that can be processed by bits_from_str()
        return self.__internal_str()

    def __internal_str(self):
        if not type(self).__dotted_single_flag_str__:
            return '%s(%s)' % (type(self).__name__, self.to_simple_str())
        contained_flags = list(self)
        if len(contained_flags) != 1:
            # This is the zero flag or a set of flags (as a result of arithmetic)
            # or a flags class member that is a superset of another flags member.
            return '%s(%s)' % (type(self).__name__, '|'.join(member.name for member in contained_flags))
        return '%s.%s' % (type(self).__name__, contained_flags[0].properties.name)

    def __repr__(self):
        contained_flags = list(self)
        if len(contained_flags) != 1:
            # This is the zero flag or a set of flags (as a result of arithmetic)
            # or a flags class member that is a superset of another flags member.
            return '<%s bits=0x%04X>' % (self.__internal_str(), int(self))
        return '<%s bits=0x%04X data=%r>' % (self.__internal_str(), contained_flags[0].properties.bits,
                                             contained_flags[0].properties.data)

    def to_simple_str(self):
        return '|'.join(member.name for member in self)

    @classmethod
    def from_simple_str(cls, s):
        """ Accepts only the output of to_simple_str(). The output of __str__() is invalid as input. """
        if not isinstance(s, str):
            raise TypeError("Expected an str instance, received %r" % (s,))
        return cls(cls.bits_from_simple_str(s))

    @classmethod
    def from_str(cls, s):
        """ Accepts both the output of to_simple_str() and __str__(). """
        if not isinstance(s, str):
            raise TypeError("Expected an str instance, received %r" % (s,))
        return cls(cls.bits_from_str(s))

    @classmethod
    def bits_from_simple_str(cls, s):
        member_names = (name.strip() for name in s.split('|'))
        member_names = filter(None, member_names)
        bits = 0
        for member_name in filter(None, member_names):
            member = cls.__all_members__.get(member_name)
            if member is None:
                raise ValueError("Invalid flag '%s.%s' in string %r" % (cls.__name__, member_name, s))
            bits |= int(member)
        return bits

    @classmethod
    def bits_from_str(cls, s):
        """ Converts the output of __str__ into an integer. """
        try:
            if len(s) <= len(cls.__name__) or not s.startswith(cls.__name__):
                return cls.bits_from_simple_str(s)
            c = s[len(cls.__name__)]
            if c == '(':
                if not s.endswith(')'):
                    raise ValueError
                return cls.bits_from_simple_str(s[len(cls.__name__)+1:-1])
            elif c == '.':
                member_name = s[len(cls.__name__)+1:]
                return int(cls.__all_members__[member_name])
            else:
                raise ValueError
        except ValueError as ex:
            if ex.args:
                raise
            raise ValueError("%s.%s: invalid input: %r" % (cls.__name__, cls.bits_from_str.__name__, s))
        except KeyError as ex:
            raise ValueError("%s.%s: Invalid flag name '%s' in input: %r" % (cls.__name__, cls.bits_from_str.__name__,
                                                                             ex.args[0], s))