1#
2# This file is part of pyasn1 software.
3#
4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pyasn1/license.html
6#
7# ASN.1 named integers
8#
9from pyasn1 import error
10
11__all__ = ['NamedValues']
12
13
14class NamedValues(object):
15    """Create named values object.
16
17    The |NamedValues| object represents a collection of string names
18    associated with numeric IDs. These objects are used for giving
19    names to otherwise numerical values.
20
21    |NamedValues| objects are immutable and duck-type Python
22    :class:`dict` object mapping ID to name and vice-versa.
23
24    Parameters
25    ----------
26    *args: variable number of two-element :py:class:`tuple`
27
28        name: :py:class:`str`
29            Value label
30
31        value: :py:class:`int`
32            Numeric value
33
34    Keyword Args
35    ------------
36    name: :py:class:`str`
37        Value label
38
39    value: :py:class:`int`
40        Numeric value
41
42    Examples
43    --------
44
45    .. code-block:: pycon
46
47        >>> nv = NamedValues('a', 'b', ('c', 0), d=1)
48        >>> nv
49        >>> {'c': 0, 'd': 1, 'a': 2, 'b': 3}
50        >>> nv[0]
51        'c'
52        >>> nv['a']
53        2
54    """
55    def __init__(self, *args, **kwargs):
56        self.__names = {}
57        self.__numbers = {}
58
59        anonymousNames = []
60
61        for namedValue in args:
62            if isinstance(namedValue, (tuple, list)):
63                try:
64                    name, number = namedValue
65
66                except ValueError:
67                    raise error.PyAsn1Error('Not a proper attribute-value pair %r' % (namedValue,))
68
69            else:
70                anonymousNames.append(namedValue)
71                continue
72
73            if name in self.__names:
74                raise error.PyAsn1Error('Duplicate name %s' % (name,))
75
76            if number in self.__numbers:
77                raise error.PyAsn1Error('Duplicate number  %s=%s' % (name, number))
78
79            self.__names[name] = number
80            self.__numbers[number] = name
81
82        for name, number in kwargs.items():
83            if name in self.__names:
84                raise error.PyAsn1Error('Duplicate name %s' % (name,))
85
86            if number in self.__numbers:
87                raise error.PyAsn1Error('Duplicate number  %s=%s' % (name, number))
88
89            self.__names[name] = number
90            self.__numbers[number] = name
91
92        if anonymousNames:
93
94            number = self.__numbers and max(self.__numbers) + 1 or 0
95
96            for name in anonymousNames:
97
98                if name in self.__names:
99                    raise error.PyAsn1Error('Duplicate name %s' % (name,))
100
101                self.__names[name] = number
102                self.__numbers[number] = name
103
104                number += 1
105
106    def __repr__(self):
107        representation = ', '.join(['%s=%d' % x for x in self.items()])
108
109        if len(representation) > 64:
110            representation = representation[:32] + '...' + representation[-32:]
111
112        return '<%s object, enums %s>' % (
113            self.__class__.__name__, representation)
114
115    def __eq__(self, other):
116        return dict(self) == other
117
118    def __ne__(self, other):
119        return dict(self) != other
120
121    def __lt__(self, other):
122        return dict(self) < other
123
124    def __le__(self, other):
125        return dict(self) <= other
126
127    def __gt__(self, other):
128        return dict(self) > other
129
130    def __ge__(self, other):
131        return dict(self) >= other
132
133    def __hash__(self):
134        return hash(self.items())
135
136    # Python dict protocol (read-only)
137
138    def __getitem__(self, key):
139        try:
140            return self.__numbers[key]
141
142        except KeyError:
143            return self.__names[key]
144
145    def __len__(self):
146        return len(self.__names)
147
148    def __contains__(self, key):
149        return key in self.__names or key in self.__numbers
150
151    def __iter__(self):
152        return iter(self.__names)
153
154    def values(self):
155        return iter(self.__numbers)
156
157    def keys(self):
158        return iter(self.__names)
159
160    def items(self):
161        for name in self.__names:
162            yield name, self.__names[name]
163
164    # support merging
165
166    def __add__(self, namedValues):
167        return self.__class__(*tuple(self.items()) + tuple(namedValues.items()))
168
169    # XXX clone/subtype?
170
171    def clone(self, *args, **kwargs):
172        new = self.__class__(*args, **kwargs)
173        return self + new
174
175    # legacy protocol
176
177    def getName(self, value):
178        if value in self.__numbers:
179            return self.__numbers[value]
180
181    def getValue(self, name):
182        if name in self.__names:
183            return self.__names[name]
184
185    def getValues(self, *names):
186        try:
187            return [self.__names[name] for name in names]
188
189        except KeyError:
190            raise error.PyAsn1Error(
191                'Unknown bit identifier(s): %s' % (set(names).difference(self.__names),)
192            )
193