# -*- coding: utf-8 -*-
"""
numconv
-------
:synopsys: Python library to convert strings to numbers and numbers to
strings.
:copyright: 2008-2009 by Gustavo Picon
:license: Apache License 2.0
:version: 2.1a
:url: http://code.tabo.pe/numconv/
:documentation:
`numconv-docs
`_
:examples:
`numconv-tests
`_
:mod:`numconv` converts a string into a number and a number into a string
using default or user supplied encoding alphabets.
constants
~~~~~~~~~
.. data:: BASE85
Alphabet defined in section 4 of :rfc:`1924`. Supposed to be a joke (it is
an April's fools RFC after all), but is quite useful because it can be used
as a base for the most common numeric conversions.
.. data:: BASE16
BASE32
BASE32HEX
BASE64
BASE64URL
Alphabets defined in :rfc:`4648`. Not really for common numeric conversion
use.
.. data:: BASE62
Useful for URL shorteners.
"""
__version__ = '2.1.0a'
# from april fool's rfc 1924
BASE85 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' \
'!#$%&()*+-;<=>?@^_`{|}~'
# rfc4648 alphabets
BASE16 = BASE85[:16]
BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
BASE32HEX = BASE85[:32]
BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
BASE64URL = BASE64[:62] + '-_'
# http://en.wikipedia.org/wiki/Base_62 useful for url shorteners
BASE62 = BASE85[:62]
class NumConv(object):
"""Class to create converter objects.
:param radix: The base that will be used in the conversions.
The default value is 10 for decimal conversions.
:param alphabet: A string that will be used as a encoding alphabet.
The length of the alphabet can be longer than the radix. In this
case the alphabet will be internally truncated.
The default value is :data:`numconv.BASE85`
:raise TypeError: when *radix* isn't an integer
:raise ValueError: when *radix* is invalid
:raise ValueError: when *alphabet* has duplicated characters
"""
def __init__(self, radix=10, alphabet=BASE85):
"basic validation and cached_map storage"
if int(radix) != radix:
raise TypeError('radix must be an integer')
if not 2 <= radix <= len(alphabet):
raise ValueError('radix must be >= 2 and <= %d' % (
len(alphabet), ))
self.radix = radix
self.alphabet = alphabet
self.cached_map = dict(zip(self.alphabet, range(len(self.alphabet))))
if len(self.cached_map) != len(self.alphabet):
raise ValueError("duplicate characters found in '%s'" % (
self.alphabet, ))
def int2str(self, num):
"""Converts an integer into a string.
:param num: A numeric value to be converted to another base as a
string.
:rtype: string
:raise TypeError: when *num* isn't an integer
:raise ValueError: when *num* isn't positive
**Examples** (taken from :file:`tests.py`):
3735928559 to hexadecimal::
>> NumConv(16).int2str(3735928559)
'DEADBEEF'
19284 to binary::
>> NumConv(2).int2str(19284)
'100101101010100'
37 to base 4 using a custom dictionary::
>> NumConv(4, 'rofl').int2str(37)
'foo'
Very large number to :data:`~numconv.BASE85`::
>> NumConv(85).int2str(2693233728041137)
'~123AFz@'
"""
if int(num) != num:
raise TypeError('number must be an integer')
if num < 0:
raise ValueError('number must be positive')
radix, alphabet = self.radix, self.alphabet
if radix in (8, 10, 16) and \
alphabet[:radix].lower() == BASE85[:radix].lower():
return ({8: '%o', 10: '%d', 16: '%x'}[radix] % num).upper()
ret = ''
while True:
ret = alphabet[num % radix] + ret
if num < radix:
break
num //= radix
return ret
def str2int(self, num):
"""Converts a string into an integer.
If possible, the built-in python conversion will be used for speed
purposes.
:param num: A string that will be converted to an integer.
:rtype: integer
:raise ValueError: when *num* is invalid
**Examples** (taken from :file:`tests.py`):
Hexadecimal 'DEADBEEF' to integer::
>> NumConv(16).str2int('DEADBEEF')
3735928559
Binary '100101101010100' to integer::
>> NumConv(2).str2int('100101101010100')
19284
Base 4 with custom encoding 'foo' to integer::
>> NumConv(4, 'rofl').str2int('foo')
37
:data:`~numconv.BASE85` '~123AFz@' to integer::
>> NumConv(85).str2int('~123AFz@')
2693233728041137
"""
radix, alphabet = self.radix, self.alphabet
if radix <= 36 and alphabet[:radix].lower() == BASE85[:radix].lower():
return int(num, radix)
ret = 0
lalphabet = alphabet[:radix]
for char in num:
if char not in lalphabet:
raise ValueError("invalid literal for radix2int() with radix "
"%d: '%s'" % (radix, num))
ret = ret * radix + self.cached_map[char]
return ret
def int2str(num, radix=10, alphabet=BASE85):
"helper for quick base conversions from integers to strings"
return NumConv(radix, alphabet).int2str(num)
def str2int(num, radix=10, alphabet=BASE85):
"helper for quick base conversions from strings to integers"
return NumConv(radix, alphabet).str2int(num)