blob: 30b3d9de8912fa8265ae0408db3f88a613f671fd [file] [log] [blame]
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -07001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright 2020 SkyWater PDK Authors
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# https://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18# SPDX-License-Identifier: Apache-2.0
19
20import dataclasses
21import dataclasses_json
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -070022import functools
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -070023import random
Tim 'mithro' Ansellee775972020-07-03 18:33:38 -070024import re
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -070025import sys
26
27from dataclasses import dataclass
28from dataclasses_json import dataclass_json
29from enum import Flag
30from typing import Optional, Tuple, Any
31
32
33def dataclass_json_passthru_config(*args, **kw):
34 return dataclasses.field(
35 *args,
36 metadata=dataclasses_json.config(
37 encoder=lambda x: x.to_json(),
38 #decoder=lambda x: x.from_json(),
39 ),
40 **kw,
41 )
42
43def dataclass_json_passthru_sequence_config(*args, **kw):
44 def to_json_sequence(s):
45 if s is None:
46 return None
47 o = []
48 for i in s:
49 if hasattr(i, 'to_json'):
50 o.append(i.to_json())
51 else:
52 o.append(i)
53 return o
54
55 return dataclasses.field(
56 *args,
57 metadata=dataclasses_json.config(
58 encoder=to_json_sequence,
59 #decoder=lambda x: x.from_json(),
60 ),
61 **kw,
62 )
63
64
65
66def comparable_to_none(cls):
67 """
68
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -070069 Examples
70 --------
71
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -070072 >>> @comparable_to_none
73 ... @dataclass(order=True)
74 ... class A:
75 ... a: int = 0
76 >>> @comparable_to_none
77 ... @dataclass(order=True)
78 ... class B:
79 ... b: Optional[A] = None
80 >>> b0 = B()
81 >>> repr(b0)
82 'B(b=None)'
83 >>> str(b0)
84 'B(b=None)'
85 >>> b1 = B(A())
86 >>> repr(b1)
87 'B(b=A(a=0))'
88 >>> str(b1)
89 'B(b=A(a=0))'
90 >>> b2 = B(A(2))
91 >>> repr(b2)
92 'B(b=A(a=2))'
93 >>> str(b2)
94 'B(b=A(a=2))'
95 >>> l = [b0, b1, b2, None]
96 >>> for i in range(0, 3):
97 ... random.shuffle(l)
98 ... l.sort()
99 ... print(l)
100 [None, B(b=None), B(b=A(a=0)), B(b=A(a=2))]
101 [None, B(b=None), B(b=A(a=0)), B(b=A(a=2))]
102 [None, B(b=None), B(b=A(a=0)), B(b=A(a=2))]
103
104 """
105 class ComparableToNoneVersion(cls):
106 def __ge__(self, other):
107 if other is None:
108 return True
109 return super().__ge__(other)
110 def __gt__(self, other):
111 if other is None:
112 return True
113 return super().__gt__(other)
114 def __le__(self, other):
115 if other is None:
116 return False
117 return super().__le__(other)
118 def __lt__(self, other):
119 if other is None:
120 return False
121 return super().__lt__(other)
122 def __eq__(self, other):
123 if other is None:
124 return False
125 return super().__eq__(other)
126 def __hash__(self):
127 return super().__hash__()
128 def __repr__(self):
129 s = super().__repr__()
130 return s.replace('comparable_to_none.<locals>.ComparableToNoneVersion', cls.__name__)
131
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700132 for a in functools.WRAPPER_ASSIGNMENTS:
133 if not hasattr(cls, a):
134 continue
135 setattr(ComparableToNoneVersion, a, getattr(cls, a))
136
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700137 return ComparableToNoneVersion
138
139
140def _is_optional_type(t):
141 """
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700142
143 Examples
144 --------
145
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700146 >>> _is_optional_type(Optional[int])
147 True
148 >>> _is_optional_type(Optional[Tuple])
149 True
150 >>> _is_optional_type(Any)
151 False
152 """
153 return hasattr(t, "__args__") and len(t.__args__) == 2 and t.__args__[-1] is type(None)
154
155
156def _get_the_optional_type(t):
157 """
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700158
159 Examples
160 --------
161
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700162 >>> _get_the_optional_type(Optional[int])
163 <class 'int'>
164 >>> _get_the_optional_type(Optional[Tuple])
165 typing.Tuple
166 >>> class A:
167 ... pass
168 >>> _get_the_optional_type(Optional[A])
169 <class '__main__.A'>
170 >>> _get_type_name(_get_the_optional_type(Optional[A]))
171 'A'
172 """
173 assert _is_optional_type(t), t
174 return t.__args__[0]
175
176
177def _get_type_name(ot):
178 """
Tim 'mithro' Ansell65fa8852020-06-11 13:54:38 -0700179
180 Examples
181 --------
182
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700183 >>> _get_type_name(int)
184 'int'
185 >>> _get_type_name(Tuple)
186 'Tuple'
187 >>> _get_type_name(Optional[Tuple])
188 'typing.Union[typing.Tuple, NoneType]'
189 """
190 if hasattr(ot, "_name") and ot._name:
191 return ot._name
192 elif hasattr(ot, "__name__") and ot.__name__:
193 return ot.__name__
194 else:
195 return str(ot)
196
197
198class OrderedFlag(Flag):
199 def __ge__(self, other):
200 if other is None:
201 return True
202 if self.__class__ is other.__class__:
203 return self.value >= other.value
204 return NotImplemented
205 def __gt__(self, other):
206 if other is None:
207 return True
208 if self.__class__ is other.__class__:
209 return self.value > other.value
210 return NotImplemented
211 def __le__(self, other):
212 if other is None:
213 return False
214 if self.__class__ is other.__class__:
215 return self.value <= other.value
216 return NotImplemented
217 def __lt__(self, other):
218 if other is None:
219 return False
220 if self.__class__ is other.__class__:
221 return self.value < other.value
222 return NotImplemented
223 def __eq__(self, other):
224 if other is None:
225 return False
226 if self.__class__ is other.__class__:
227 return self.value == other.value
228 return NotImplemented
229 def __hash__(self):
230 return hash(self._name_)
231
232
Tim 'mithro' Ansellee775972020-07-03 18:33:38 -0700233def extract_numbers(s):
234 """Create tuple with sequences of numbers converted to ints.
235
236 >>> extract_numbers("pwr_template13x10")
237 ('pwr_template', 13, 'x', 10)
238 >>> extract_numbers("vio_10_10_1")
239 ('vio_', 10, '_', 10, '_', 1)
240 """
241 bits = []
242 for m in re.finditer("([^0-9]*)([0-9]*)", s):
243 if m.group(1):
244 bits.append(m.group(1))
245 if m.group(2):
246 bits.append(int(m.group(2)))
247 return tuple(bits)
248
249
250def sortable_extracted_numbers(s):
251 """Create output which is sortable by numeric values in string.
252
253 >>> sortable_extracted_numbers("pwr_template13x10")
254 ('pwr_template', '0000000013', 'x', '0000000010')
255 >>> sortable_extracted_numbers("vio_10_10_1")
256 ('vio_', '0000000010', '_', '0000000010', '_', '0000000001')
257
258 >>> l = ['a1', 'a2b2', 'a10b10', 'b2', 'a8b50', 'a10b1']
259 >>> l.sort()
260 >>> print('\\n'.join(l))
261 a1
262 a10b1
263 a10b10
264 a2b2
265 a8b50
266 b2
267 >>> l.sort(key=sortable_extracted_numbers)
268 >>> print('\\n'.join(l))
269 a1
270 a2b2
271 a8b50
272 a10b1
273 a10b10
274 b2
275
276 """
277 zero_pad_str = '%010i'
278 bits = extract_numbers(s)
279 o = []
280
281 for b in bits:
282 if not isinstance(b, str):
283 assert isinstance(b, int), (b, bits)
284 assert len(str(b)) < len(zero_pad_str % 0)
285 b = zero_pad_str % b
286 o.append(b)
287 return tuple(o)
288
289
290
Tim 'mithro' Ansell1d603e72020-05-14 17:52:04 -0700291if __name__ == "__main__":
292 import doctest
293 doctest.testmod()