forked from dhondta/python-codext
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_base.py
More file actions
executable file
·164 lines (133 loc) · 5.58 KB
/
Copy path_base.py
File metadata and controls
executable file
·164 lines (133 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# -*- coding: UTF-8 -*-
"""Generic baseN functions.
"""
from math import log
from six import integer_types, string_types
from string import ascii_lowercase as lower, ascii_uppercase as upper, digits, printable
from types import FunctionType
from ..__common__ import *
class BaseError(ValueError):
pass
class BaseDecodeError(BaseError):
pass
class BaseEncodeError(BaseError):
pass
def _generate_charset(n):
""" Generate a characters set.
:param n: size of charset
"""
if 1 < n <= len(printable):
return printable[:n]
elif len(printable) < n < 256:
return "".join(chr(i) for i in range(n))
raise ValueError("Bad size of character set")
def _get_charset(charset, p=""):
""" Characters set selection function. It allows to define charsets in many different ways.
:param charset: charset object, can be a string (the charset itself), a function (that chooses the right charset
depending on the input parameter) or a dictionary (either by exact key or by pattern matching)
:param p: the parameter for choosing the charset
"""
# case 1: charset is a function, so return its result
if isinstance(charset, FunctionType):
return charset(p)
# case 2: charset is a string, so return it
elif isinstance(charset, string_types):
return charset
# case 3: charset is a dict with keys '' and 'inv', typically for a charset using lowercase and uppercase characters
# that can be inverted
elif isinstance(charset, dict) and list(charset.keys()) == ["", "inv"]:
return charset["inv" if re.match(r"[-_]inv(erted)?$", p) else ""]
# case 4: charset is a dict, but not with the specific keys '' and 'inv', so consider it as pattern-charset pairs
elif isinstance(charset, dict):
# try to handle [p]arameter as a simple key
try:
return charset[p]
except KeyError:
pass
# or handle [p]arameter as a pattern
default, n = None, None
for pattern, cset in charset.items():
n = len(cset)
if pattern == "":
default = cset
continue
if re.match(pattern, p):
return cset
# special case: the given [p]arameter can be the charset itself if it has the right length
p = re.sub(r"^[-_]+", "", p)
if len(p) == n:
return p
# or simply rely on key ''
if default is not None:
return default
raise ValueError("Bad charset descriptor")
# generic base en/decoding functions
def base_encode(input, charset, errors="strict", exc=BaseEncodeError):
""" Base-10 to base-N encoding.
:param input: input (str or int) to be decoded
:param charset: base-N characters set
:param errors: errors handling marker
:param exc: exception to be raised in case of error
"""
i = input if isinstance(input, integer_types) else s2i(input)
n = len(charset)
r = ""
while i > 0:
i, c = divmod(i, n)
r = charset[c] + r
return r
def base_decode(input, charset, errors="strict", exc=BaseDecodeError):
""" Base-N to base-10 decoding.
:param input: input to be decoded
:param charset: base-N characters set
:param errors: errors handling marker
:param exc: exception to be raised in case of error
"""
i, n = 0, len(charset)
for k, c in enumerate(input):
try:
i = i * n + charset.index(c)
except ValueError:
handle_error("base", errors, exc, decode=True)(c, k)
return base_encode(i, [chr(j) for j in range(256)], errors, exc)
# base codec factory functions
def base(charset, pattern, pow2=False, encode_template=base_encode, decode_template=base_decode, name=None, **kwargs):
""" Base-N codec factory.
:param charset: charset selection function
:param pattern: matching pattern for the codec name (first capturing group is used as the parameter for selecting
the charset)
:param pow2: whether the base codec's N is a power of 2
"""
n = len(_get_charset(charset))
nb = log(n, 2)
if pow2 and nb != int(nb):
raise BaseError("Bad charset ; {} is not a power of 2".format(n))
def encode(param=""):
a = _get_charset(charset, param)
def _encode(input, errors="strict"):
return encode_template(input, a, errors), len(input)
return _encode
def decode(param=""):
a = _get_charset(charset, param)
def _decode(input, errors="strict"):
return decode_template(input, a, errors), len(input)
return _decode
kwargs['len_charset'] = n
kwargs['printables_rate'] = 1.
n = "base{}".format(n) if name is None else name
add(n, encode, decode, pattern, entropy=nb, guess=[n], **kwargs)
def base_generic():
""" Base-N generic codec. """
def encode(n):
a = _generate_charset(int(n))
def _encode(input, errors="strict"):
return base_encode(input, a, errors), len(input)
return _encode
def decode(n):
a = _generate_charset(int(n))
def _decode(input, errors="strict"):
return base_decode(input, a, errors), len(input)
return _decode
add("base", encode, decode, r"^base[-_]?([2-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:[-_]generic)?$",
guess=["base%d-generic" % i for i in range(2, 255)], entropy=lambda e, n: log(int(n.split("-")[0][4:]), 2),
len_charset=lambda n: int(n.split("-")[0][4:]), printables_rate=1., category="base-generic", penalty=.4)