Skip to content

Commit b9af1cb

Browse files
committed
Added codec: base91-alternate
1 parent 29aeb21 commit b9af1cb

2 files changed

Lines changed: 81 additions & 38 deletions

File tree

codext/base/base91.py

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
- decodes file content to str (read)
88
- encodes file content from str to bytes (write)
99
"""
10-
from string import ascii_lowercase as lower, ascii_uppercase as upper, digits
11-
12-
from ..__common__ import *
1310
from ._base import digits, lower, upper
11+
from ..__common__ import *
1412

1513
# no __examples__ ; handled manually in tests/test_base.py
16-
__guess__ = ["base91", "base91-inv"]
14+
__guess__ = ["base91", "base91-inv", "base91-alt", "base91-alt-inv"]
1715

1816

1917
B91 = {
20-
'': upper + lower + digits + "!#$%&()*+,./:;<=>?@[]^_`{|}~\"",
21-
'inv': lower + upper + digits + "!#$%&()*+,./:;<=>?@[]^_`{|}~\"",
18+
'': upper + lower + digits + "!#$%&()*+,./:;<=>?@[]^_`{|}~\"",
19+
'inv': lower + upper + digits + "!#$%&()*+,./:;<=>?@[]^_`{|}~\"",
20+
'alt': "!#$%&'()*+,-./" + digits + ":;<=>?@" + upper + "[\\]^_" + lower + "{|}",
21+
'alt-inv': "!#$%&'()*+,-./" + digits + ":;<=>?@" + lower + "[\\]^_" + upper + "{|}",
2222
}
2323

2424

@@ -31,57 +31,89 @@ class Base91DecodeError(ValueError):
3131

3232

3333
def base91_encode(mode):
34-
b91 = B91[["inv", ""][mode == ""]]
34+
mode = mode.replace("alternate", "alt").replace("inverted", "inv").replace("_", "-").lstrip("-")
35+
b91 = B91[mode if mode in B91.keys() else ""]
3536
def encode(text, errors="strict"):
36-
t = b(text)
37-
s = ""
38-
bits = ""
39-
for c in t:
40-
bits = bin(__ord(c))[2:].zfill(8) + bits
41-
if len(bits) > 13:
42-
n = int(bits[-13:], 2)
43-
if n > 88:
44-
bits = bits[:-13]
37+
t, s, bits = b(text), "", ""
38+
if mode.startswith("alt"):
39+
while len(bits) < 13 and t:
40+
bits += "{:08b}".format(__ord(t[0]))
41+
t = t[1:]
42+
while len(bits) > 13 or t:
43+
n = int(bits[:13], 2)
44+
s += b91[n // 91] + b91[n % 91]
45+
bits = bits[13:]
46+
while len(bits) < 13 and t:
47+
bits += "{:08b}".format(__ord(t[0]))
48+
t = t[1:]
49+
if len(bits) > 0:
50+
if len(bits) < 7:
51+
bits += "0" * (6 - len(bits))
52+
s += b91[int(bits, 2)]
4553
else:
46-
n = int(bits[-14:], 2)
47-
bits = bits[:-14]
48-
s += b91[n % 91] + b91[n // 91]
49-
if len(bits) > 0:
50-
n = int(bits, 2)
51-
s += b91[n % 91]
52-
if len(bits) > 7 or n > 90:
53-
s += b91[n // 91]
54+
bits += "0" * (13 - len(bits))
55+
n = int(bits, 2)
56+
print(bits, n)
57+
s += b91[n // 91] + b91[n % 91]
58+
else:
59+
for c in t:
60+
bits = bin(__ord(c))[2:].zfill(8) + bits
61+
if len(bits) > 13:
62+
n = int(bits[-13:], 2)
63+
if n > 88:
64+
bits = bits[:-13]
65+
else:
66+
n = int(bits[-14:], 2)
67+
bits = bits[:-14]
68+
s += b91[n % 91] + b91[n // 91]
69+
if len(bits) > 0:
70+
n = int(bits, 2)
71+
s += b91[n % 91]
72+
if len(bits) > 7 or n > 90:
73+
s += b91[n // 91]
5474
return s, len(t)
5575
return encode
5676

5777

5878
def base91_decode(mode):
59-
b91 = {c: i for i, c in enumerate(B91[["inv", ""][mode == ""]])}
79+
mode = mode.replace("alternate", "alt").replace("inverted", "inv").replace("_", "-").lstrip("-")
80+
b91 = {c: i for i, c in enumerate(B91[mode if mode in B91.keys() else ""])}
6081
def decode(text, errors="strict"):
61-
t = b(text)
62-
s = ""
63-
bits = ""
82+
t, s, bits, alt = b(text), "", "", mode.startswith("alt")
83+
err = "'base91' codec can't decode character '%s' in position %d"
6484
for i in range(0, len(t), 2):
6585
try:
66-
n = b91[__chr(t[i])]
86+
n = b91[__chr(t[i])] * [1, 91][alt]
6787
except KeyError:
68-
raise Base91DecodeError("'base91' codec can't decode character '%s' in position %d" % (__chr(t[i]), i))
88+
raise Base91DecodeError(err % (__chr(t[i]), i))
6989
try:
7090
j = i + 1
71-
n += b91[__chr(t[j])] * 91
91+
n += b91[__chr(t[j])] * [91, 1][alt]
7292
except IndexError:
7393
pass
7494
except KeyError:
75-
raise Base91DecodeError("'base91' codec can't decode character '%s' in position %d" % (__chr(t[j]), j))
76-
bits = bin(n)[2:].zfill([14, 13][n & 8191 > 88]) + bits
77-
while len(bits) > 8:
78-
s += chr(int(bits[-8:], 2))
79-
bits = bits[:-8]
80-
if len(bits) > 0 and not set(bits) == {"0"}:
95+
raise Base91DecodeError(err % (__chr(t[j]), j))
96+
if alt:
97+
bits += "{:013b}".format(n)
98+
while 8 <= len(bits):
99+
s += chr(int(bits[0:8], 2))
100+
bits = bits[8:]
101+
else:
102+
bits = bin(n)[2:].zfill([14, 13][n & 8191 > 88]) + bits
103+
while len(bits) > 8:
104+
s += chr(int(bits[-8:], 2))
105+
bits = bits[:-8]
106+
if alt and len(t) % 2 == 1:
107+
bits += "{:06b}".format(b91[__chr(t[-1])])
108+
while 8 <= len(bits):
109+
s += chr(int(bits[:8], 2))
110+
bits = bits[8:]
111+
elif not alt and len(bits) > 0 and not set(bits) == {"0"}:
81112
s += chr(int(bits, 2))
82113
return s, len(t)
83114
return decode
84115

85116

86-
add("base91", base91_encode, base91_decode, r"^base[-_]?91(|[-_]inv(?:erted)?)$", entropy=6.5)
117+
add("base91", base91_encode, base91_decode, r"^base[-_]?91((?:|[-_]alt(?:ernate)?)(?:|[-_]inv(?:erted)?)?)$",
118+
entropy=6.5)
87119

tests/test_base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,17 @@ def test_codec_base91(self):
209209
self.assertIsNotNone(codecs.decode("AD", "base91"))
210210
self.assertRaises(ValueError, codecs.decode, "\xff", "base91")
211211
self.assertRaises(ValueError, codecs.decode, "a\xff", "base91")
212+
B91A = "Jx&[jv4S3Wg>,71@Jk"
213+
self.assertEqual(codecs.encode(STR, "base91-alt"), B91A)
214+
self.assertEqual(codecs.encode(b(STR), "base91-alt"), b(B91A))
215+
self.assertEqual(codecs.decode(B91A, "base91_alt"), STR)
216+
self.assertEqual(codecs.decode(b(B91A), "base91_alt"), b(STR))
217+
B91A = "jX&[JV4s3wG>,71@jK"
218+
self.assertEqual(codecs.encode(STR, "base91-alt-inv"), B91A)
219+
self.assertEqual(codecs.decode(B91A, "base91_alt_inv"), STR)
220+
self.assertIsNotNone(codecs.encode("\x00\x00", "base91-alt"))
221+
self.assertIsNotNone(codecs.decode("abc", "base91-alt"))
222+
self.assertIsNotNone(codecs.decode("AD", "base91_alt"))
212223

213224
def test_codec_base100(self):
214225
if PY3:

0 commit comments

Comments
 (0)