Coverage for utilities/ordering.py: 65%
35 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1import re
3__all__ = (
4 'naturalize',
5 'naturalize_interface',
6)
8INTERFACE_NAME_REGEX = r'(^(?P<type>[^\d\.:]+)?)' \
9 r'((?P<slot>\d+)/)?' \
10 r'((?P<subslot>\d+)/)?' \
11 r'((?P<position>\d+)/)?' \
12 r'((?P<subposition>\d+)/)?' \
13 r'((?P<id>\d+))?' \
14 r'(:(?P<channel>\d+))?' \
15 r'(\.(?P<vc>\d+))?' \
16 r'(?P<remainder>.*)$'
19def naturalize(value, max_length, integer_places=8):
20 """
21 Take an alphanumeric string and prepend all integers to `integer_places` places to ensure the strings
22 are ordered naturally. For example:
24 site9router21
25 site10router4
26 site10router19
28 becomes:
30 site00000009router00000021
31 site00000010router00000004
32 site00000010router00000019
34 :param value: The value to be naturalized
35 :param max_length: The maximum length of the returned string. Characters beyond this length will be stripped.
36 :param integer_places: The number of places to which each integer will be expanded. (Default: 8)
37 """
38 if not value: 38 ↛ 40line 38 didn't jump to line 40 because the condition on line 38 was always true
39 return value
40 output = []
41 for segment in re.split(r'(\d+)', value):
42 if segment.isdigit():
43 output.append(segment.rjust(integer_places, '0'))
44 elif segment:
45 output.append(segment)
46 ret = ''.join(output)
48 return ret[:max_length]
51def naturalize_interface(value, max_length):
52 """
53 Similar in nature to naturalize(), but takes into account a particular naming format adapted from the old
54 InterfaceManager.
56 :param value: The value to be naturalized
57 :param max_length: The maximum length of the returned string. Characters beyond this length will be stripped.
58 """
59 output = ''
60 match = re.search(INTERFACE_NAME_REGEX, value)
61 if match is None: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 return value
64 # First, we order by slot/position, padding each to four digits. If a field is not present,
65 # set it to 9999 to ensure it is ordered last.
66 for part_name in ('slot', 'subslot', 'position', 'subposition'):
67 part = match.group(part_name)
68 if part is not None: 68 ↛ 69line 68 didn't jump to line 69 because the condition on line 68 was never true
69 output += part.rjust(4, '0')
70 else:
71 output += '9999'
73 # Append the type, if any.
74 if match.group('type') is not None:
75 output += match.group('type')
77 # Append any remaining fields, left-padding to six digits each.
78 for part_name in ('id', 'channel', 'vc'):
79 part = match.group(part_name)
80 if part is not None:
81 output += part.rjust(6, '0')
82 else:
83 output += '......'
85 # Finally, naturalize any remaining text and append it
86 if match.group('remainder') is not None and len(output) < max_length: 86 ↛ 90line 86 didn't jump to line 90 because the condition on line 86 was always true
87 remainder = naturalize(match.group('remainder'), max_length - len(output))
88 output += remainder
90 return output[:max_length]