Passed
Push — master ( 96f67f...194dfd )
by Ramon
05:02
created

bika.lims.alphanumber   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 33
eloc 108
dl 0
loc 156
rs 9.76
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A Alphanumber.__format__() 0 14 4
A Alphanumber.__gt__() 0 2 1
A Alphanumber.__repr__() 0 2 1
A Alphanumber.parts() 0 24 4
A Alphanumber.__int__() 0 2 1
A Alphanumber.format() 0 2 1
A Alphanumber.__add__() 0 4 1
A Alphanumber.__init__() 0 12 3
A Alphanumber.__lt__() 0 2 1
A Alphanumber.__str__() 0 2 1
A Alphanumber.__index__() 0 2 1
A Alphanumber.__sub__() 0 4 1
A Alphanumber.__eq__() 0 2 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
B to_decimal() 0 28 5
B to_alpha() 0 25 7
1
import re
2
3
from bika.lims import api
4
5
_marker = object()
6
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
7
8
9
class Alphanumber(object):
10
    """Represents an alphanumeric number
11
    """
12
13
    def __init__(self, number=0, num_chars=3, num_digits=3, alphabet=ALPHABET):
14
        if num_chars < 1:
15
            raise ValueError("num_chars param is lower than 1")
16
        if num_digits < 1:
17
            raise ValueError("num_digits param is lower than 1")
18
        self.alpha_str = None
19
        self.alphabet = alphabet
20
        self.num_chars = num_chars
21
        self.num_digits = num_digits
22
        self.int10 = to_decimal(number, alphabet=alphabet)
23
        self.alpha_format = '%sa%sd' % (self.num_chars, self.num_digits)
24
        self.alpha_str = self.__format__(self.alpha_format)
25
26
    def __int__(self):
27
        return self.int10
28
29
    def __index__(self):
30
        return self.__int__()
31
32
    def __str__(self):
33
        return self.alpha_str
34
35
    def __repr__(self):
36
        return self.__str__()
37
38
    def __add__(self, other):
39
        number = self.__int__() + int(other)
40
        return Alphanumber(number=number, num_chars=self.num_chars,
41
                           num_digits=self.num_digits, alphabet=self.alphabet)
42
43
    def __sub__(self, other):
44
        number = self.__int__() - int(other)
45
        return Alphanumber(number=number, num_chars=self.num_chars,
46
                           num_digits=self.num_digits, alphabet=self.alphabet)
47
48
    def __lt__(self, other):
49
        return self.int10 < int(other)
50
51
    def __gt__(self, other):
52
        return self.int10 > int(other)
53
54
    def __eq__(self, other):
55
        return self.int10 == int(other)
56
57
    def __format__(self, format):
58
        if self.alpha_str and self.alpha_format == format:
59
            # no need to resolve parts again
60
            return self.alpha_str
61
62
        elif self.alpha_format != format:
63
            return to_alpha(self.int10, format, self.alphabet).format(format)
64
65
        base_format = "{alpha:%s>%s}{number:0%sd}" % (self.alphabet[0],
66
                                                      self.num_chars,
67
                                                      self.num_digits)
68
        alpha, number = self.parts()
69
        values = dict(alpha=alpha, number=number)
70
        return base_format.format(**values)
71
72
    def format(self, format):
73
        return self.__format__(format)
74
75
    def parts(self):
76
        """Returns the alphanumeric parts (chars + digits) of this Alphanum
77
        """
78
        def get_alpha(alpha_index, alphabet):
79
            if alpha_index >= len(alphabet):
80
                lead = get_alpha(alpha_index / len(alphabet), alphabet)
81
                trail = alphabet[alpha_index % len(alphabet)]
82
                return "{}{}".format(lead, trail)
83
            return alphabet[alpha_index]
84
85
        max_digits = 10 ** self.num_digits - 1
86
        alpha_number = max_digits
87
        alpha_index = 0
88
        while alpha_number < abs(self.int10):
89
            alpha_number += max_digits
90
            alpha_index += 1
91
92
        alpha_number = abs(self.int10) - alpha_number + max_digits
93
        alpha = get_alpha(alpha_index, self.alphabet)
94
        if len(alpha) > self.num_chars:
95
            raise ValueError("Out of bounds. Requires {} chars, {} set"
96
                             .format(len(alpha), self.num_chars))
97
98
        return alpha, alpha_number
99
100
101
def to_alpha(number, format, alphabet=ALPHABET, default=_marker):
102
    """Returns an Alphanumber object that represents the number in accordance
103
    with the format specified.
104
    :param number: a number representation used to create the Alphanumber
105
    :param format: the format to use. eg. '2a3d' for 2 chars and 3 digits
106
    :param alphabet: alphabet to use
107
    :type number: int, string, Alphanumber, float
108
    :type format: string
109
    :type alphabet: string
110
    """
111
    match = re.match(r"^(\d+)a(\d+)d", format)
112
    if not match or not match.groups() or len(match.groups()) != 2:
113
        if default is not _marker:
114
            return default
115
        raise ValueError("Format not supported: {}".format(format))
116
    matches = match.groups()
117
    num_chars = int(matches[0])
118
    num_digits = int(matches[1])
119
    try:
120
        return Alphanumber(number=number, num_chars=num_chars,
121
                           num_digits=num_digits, alphabet=alphabet)
122
    except ValueError as e:
123
        if default is not _marker:
124
            return default
125
        raise e
126
127
128
def to_decimal(alpha_number, alphabet=ALPHABET, default=_marker):
129
    """Converts an alphanumeric code (e.g AB12) to an integer
130
    :param alpha_number: representation of an alphanumeric code
131
    :param alphabet: alphabet to use when alpha_number is a non-int string
132
    :type number: int, string, Alphanumber, float
133
    :type alphabet: string
134
    """
135
    num = api.to_int(alpha_number, default=None)
136
    if num is not None:
137
        return num
138
139
    alpha_number = str(alpha_number)
140
    regex = re.compile(r"([A-Z]+)(\d+)", re.IGNORECASE)
141
    matches = re.findall(regex, alpha_number)
142
    if not matches:
143
        if default is not _marker:
144
            return default
145
        raise ValueError("Not a valid alpha number: {}".format(alpha_number))
146
147
    alpha = matches[0][0]
148
    number = int(matches[0][1])
149
    max_num = 10 ** len(matches[0][1]) - 1
150
    len_alphabet = len(alphabet)
151
    for pos_char, alpha_char in enumerate(reversed(alpha)):
152
        index_char = alphabet.find(alpha_char)
153
        number += (index_char * max_num * len_alphabet ** pos_char)
154
155
    return number
156