Source code for xotl.tools.bases
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#
"""Integer encoding and decoding in different bases.
"""
_DEFAULT_TABLE = "0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
_MAX_BASE = len(_DEFAULT_TABLE)
_DEFAULT_BASE = _MAX_BASE
def _check_base(base):
"""Check a base to be used in string to integer conversions.
Return a tuple (base, table) if valid or raise an exception.
"""
if isinstance(base, int):
table = _DEFAULT_TABLE
if not (1 < base <= _MAX_BASE):
raise ValueError("`base` must be between 2 and %s" % _MAX_BASE)
elif isinstance(base, str):
table = base
base = len(table)
else:
msg = (
"`base` must be an integer (base) or a string (table) with "
'length greater or equal to 2; %s "%s" given'
)
raise TypeError(msg % (type(base).__name__, base))
return base, table
[docs]def int2str(number, base=_DEFAULT_BASE):
"""Return the string representation of an integer using a base.
:param base: The base.
:type base: Either an integer or a string with a custom table.
Examples::
>>> int2str(65535, 16)
'ffff'
>>> int2str(65535)
'h31'
>>> int2str(65110208921, 'merchise')
'ehimseiemsce'
>>> int2str(651102, 2)
'10011110111101011110'
"""
base, table = _check_base(base)
sign = "" if number >= 0 else "-"
number = abs(number)
res = table[0] if number == 0 else ""
while number:
number, idx = divmod(number, base)
res = table[idx] + res
return str(sign + res)
[docs]def str2int(src, base=_DEFAULT_BASE):
"""Return the integer decoded from a string representation using a base.
:param base: The base.
:type base: Either an integer or a string with a custom table.
Examples::
>>> str2int('ffff', 16)
65535
>>> str2int('1c', 16) == int('1c', 16)
True
>>> base = 'merchise'
>>> number = 65110208921
>>> str2int(int2str(number, base), base) == number
False
>>> base = 32
>>> str2int(int2str(number, base), base) == number
True
"""
base, table = _check_base(base)
if src.startswith("-"):
sign = -1
i = 1
else:
sign = 1
i = 0
res = 0
while i < len(src):
res *= base
res += table.index(src[i])
i += 1
return sign * res
class BaseConvertor:
"""Base class that implements conversion algorithms based on a simple
lookup table and a bit mask.
Derived classes *must* provide a `table` attribute with the table of
digits to use.
"""
@classmethod
def inttobase(cls, num):
"""Converts an integer to a base representation using the class' table."""
return int2str(num, base=cls.table)
@classmethod
def basetoint(cls, istr):
"""Converts a base representation to a integer using the class' table."""
table = cls.table
if cls.case_insensitive:
table = table.lower()
return str2int(istr, base=table)
[docs]class B32(BaseConvertor):
"""Handles base-32 conversions.
In base 32, each 5-bits chunks are represented by a single "digit". Digits
comprises all symbols in 0..9 and a..v.
>>> B32.inttobase(32) == '10'
True
>>> B32.basetoint('10')
32
"""
table = "0123456789abcdefghijklmnopqrstuv"
case_insensitive = True
[docs]class B64(BaseConvertor):
"""Handles [a kind of] base 64 conversions.
This **is not standard base64**, but a reference-friendly base 64 to help
the use case of generating a short reference.
In base 64, each 6-bits chunks are represented by a single "digit".
Digits comprises all symbols in 0..9, a..z, A..Z and the three symbols:
`()[`.
>>> B64.inttobase(64) == '10'
True
>>> B64.basetoint('10')
64
.. warning::
In this base, letters **are** case sensitive::
>>> B64.basetoint('a')
10
>>> B64.basetoint('A')
36
"""
table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZ()["
case_insensitive = False
class B64symbolic(B64):
"""Same as B64 but uses no capital letters and lots of symbols."""
table = "0123456789abcdefghijklmnopqrstuvwxyz" ":;><,._=!@#$^*/?\\{}%`|\"()[~'"
case_insensitive = True