diff --git a/.gitignore b/.gitignore index c208b66..b88f620 100644 --- a/.gitignore +++ b/.gitignore @@ -131,6 +131,7 @@ dmypy.json # VSCode .vscode/ +*.code-workspace # Pre-commit .pre-commit-config.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 686dadf..a2884f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.3.1 + +- Optimization + ## 1.3.0 - Added: Flags to specify dotting style: end with dot, wrap in dots, use delimeter-dots, or any combination @@ -13,7 +17,7 @@ ## 1.1.1 -Cleanup +- Cleanup ## 1.1.0 diff --git a/INTRODUCTION.md b/INTRODUCTION.md index ea116b2..3a29f31 100644 --- a/INTRODUCTION.md +++ b/INTRODUCTION.md @@ -1,7 +1,7 @@ # CYRILLIC NUMERAL SYSTEM ## 1. Numerals -Church numeral system (*further CU*) has individual letters assigned to represent numbers from 1 to 9 in registries from digits to hundreds, for a total of 27 numerals. There's no zero numeral. +Cyrillic numeral system (*further CU*) has individual letters assigned to represent numbers from 1 to 9 in registries from digits to hundreds, for a total of 27 numerals. There's no zero numeral. CU|Arabic|CU|Arabic|CU|Arabic ---|---|---|---|---|--- diff --git a/README.md b/README.md index bfc86cc..161c685 100644 --- a/README.md +++ b/README.md @@ -24,26 +24,28 @@ See [Introduction](./INTRODUCTION.md) to learn about CU numeral system. a = cunumbers.to_cu(1) # Convert a CU number to Arabic - # Requires str, returns int + # Requires non-empty str, returns int b = cunumbers.to_arab("а҃") -"Delimiter" and "plain" style numbers are supported in both directions. "Delimeter" style is default for CU-wise conversions. +"Delimiter" and "plain" style numbers are supported in both directions. "Delimeter" style is default for CU-wise conversion. - # Use CU_PLAIN flag to use "plain" style in CU-wise conversion +Several falgs can be used with `to_cu()` method: + + # CU_PLAIN flag sets conversion to "plain" style c = cunumbers.to_cu(111111, CU_PLAIN) - # Use CU_NOTITLO flag to omit "titlo" + # CU_NOTITLO flag omits "titlo" output - d = cunumbers.to_cu(11000, CU_PLAIN + CU_NOTITLO) + d = cunumbers.to_cu(11000, CU_PLAIN | CU_NOTITLO) - # Use following flags in CU-wise conversion to add dot-styling: + # Following flags control dot styling: + # # CU_ENDDOT - append dot at the end # CU_WRAPDOT - append dot at both ends - # CU_DELIMDOT - add dot between each group in "delimeter" style. - # Sets conversion to "delim" style. - # CU_ALLDOT = combine CU_WRAPDOT and CU_DELIMDOT + # CU_DELIMDOT - add dot separator between digit groups. Sets conversion to "delim" style + # CU_ALLDOT - combine CU_WRAPDOT and CU_DELIMDOT ## Contributing diff --git a/cunumbers/__init__.py b/cunumbers/__init__.py index 72507db..4aaf871 100644 --- a/cunumbers/__init__.py +++ b/cunumbers/__init__.py @@ -1 +1,10 @@ -from .cunumbers import * +__all__ = [ + "to_cu", + "to_arab", + "CU_PLAIN", + "CU_NOTITLO", + "CU_ENDDOT", + "CU_DELIMDOT", + "CU_WRAPDOT", + "CU_ALLDOT", +] diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index 7a4415c..1935baa 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -1,209 +1,269 @@ # -*- coding: UTF-8 -*- # For licensing information see LICENSE file included in the project's root directory. # To learn about Cyrillic numeral system (further CU), see INTRODUCTION.md -""" -Module for number conversion between Arabic and Cyrillic numeral systems. -""" +"Module for number conversion between Arabic and Cyrillic numeral systems." import re -_CU_DELIM = 0x1 # Write in delim style +CU_DELIM = 0x1 # Write in delim style CU_PLAIN = 0x10 # Read/write in plain style CU_NOTITLO = 0x100 # DO NOT append titlo CU_ENDDOT = 0x1000 # Append dot -_CU_PREDOT = 0x10000 # Prepend dot -CU_DELIMDOT = 0x100001 # Delimeter dots (delim mode only) -CU_WRAPDOT = CU_ENDDOT + _CU_PREDOT # Wrap in dots -CU_ALLDOT = CU_ENDDOT + _CU_PREDOT + CU_DELIMDOT # Sandwich and delimeter dots - -_cu_digits = "авгдєѕзиѳ" -_cu_tens = "іклмнѯѻпч" -_cu_hundreds = "рстуфхѱѿц" -_cu_thousand = "҂" -_cu_titlo = "҃" -_cu_dot = "." - -_cu_null = "\uE000" # A placeholder character to represent zero in CU numbers -_cu_dict = "{0}{1}{0}{2}{0}{3}".format(_cu_null, _cu_digits, _cu_tens, _cu_hundreds) - -_cu_swap_regex = ["(%s)([%s])" % (_cu_tens[0], _cu_digits), "\g<2>\g<1>"] -_cu_base_regex = "[{0}]?(?:[{2}]?{3}|[{1}]?[{2}]?)".format( - _cu_hundreds, _cu_tens[1:], _cu_digits, _cu_tens[0] -) -_cu_delim_regex = "(%s*%s)" % (_cu_thousand, _cu_base_regex) -_cu_plain_regex = "(%s+[%s]{1}|(?:%s)$)" % ( - _cu_thousand, - _cu_dict.replace(_cu_null, ""), - _cu_base_regex, +CU_PREDOT = 0x10000 # Prepend dot +CU_DELIMDOT = 0x100000 | CU_DELIM # Delimeter dots (delim mode only) +CU_WRAPDOT = CU_ENDDOT | CU_PREDOT # Wrap in dots +CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots + +cu_digits = "авгдєѕзиѳ" # CU digit numerals +cu_tens = "іклмнѯѻпч" # CU tens numerals +cu_hundreds = "рстуфхѱѿц" # CU hundreds numerals +cu_thousand = "҂" # "Thousand" mark +cu_titlo = "҃" # "Titlo" decorator +cu_dot = "." # Dot decorator + +cu_null = "\uE000" # A placeholder character to represent zero in CU numbers +cu_dict = "{0}{1}{0}{2}{0}{3}".format( # CU numerals dictionary + cu_null, cu_digits, cu_tens, cu_hundreds ) +cu_group_regex = ( # Regex for a basic CU number x < 1000 + "[{0}]?(?:[{2}]?{3}|[{1}]?[{2}]?)".format( + cu_hundreds, cu_tens[1:], cu_digits, cu_tens[0] + ) +) +cu_delim_regex = "({0}*{1})".format( # Regex for a digit group in "delim" style + cu_thousand, cu_group_regex +) +cu_plain_regex = ( # Regex for a single digit in "plain" style + "({0}+[{1}]{2}|(?:{3})$)".format( + cu_thousand, + cu_dict.replace(cu_null, ""), + "{1}", + cu_group_regex, + ) +) -def _chflag(flags, flag): - """Check a flag.""" - return False if flags & flag == 0 else True +class CUNumber: + def __init__(self, input, flags=0): + self.cu = None + self.arabic = input + self.flags = flags -def _to_cu_digit(digit=0, registry=0, multiplier=0): - """Process an arabic digit.""" + def get(self): + return self.cu - if digit: - return _cu_thousand * multiplier + _cu_dict[10 * registry + digit] - else: # Skip if @digit = 0 - return "" + def hasFlag(self, flag): + """Check a flag.""" + return False if self.flags & flag == 0 else True -def _to_cu_hundred(hundred=0, group=0, registry=0, result=""): - """Process an arabic hundred group.""" # DELIM MODE ONLY + def stripDelimDots(self): + "Strip delimeter dots unless CU_DELIMDOT is set." - if hundred: - sub_result = _to_cu_digit(hundred % 10, registry) + result - if hundred // 10: - return _to_cu_hundred(hundred // 10, group, registry + 1, sub_result) - else: - # Swap digits in 11-19 - sub_result = re.sub(_cu_swap_regex[0], _cu_swap_regex[1], sub_result) - # Use dot delimeters by default to prevent ambiguity - return _cu_dot + _cu_thousand * group + sub_result - else: # Skip if @hundred = 0 - return "" - - -def _to_cu_number_delim(input, group=0, result="", flags=0): - """Process an arabic number per hundred group.""" - # @index is current hundred group - - # print("DELIM MODE") - sub_result = ( - _to_cu_hundred(input % 1000, group) + result - ) # Process rightmost hundred group - if input // 1000: - # Iterate over each hundred group, increasing @group index - return _to_cu_number_delim(input // 1000, group + 1, sub_result, flags) - else: - # Drop all dots that aren't making difference unless CU_DELIMDOT - if not _chflag(flags, CU_DELIMDOT): - # print("Dropping leftover delim dots") - sub_result = re.sub( + if not self.hasFlag(CU_DELIMDOT): + self.cu = re.sub( "(\{0}(?!{1}$)|(?", self.cu) + return self + + def prependDot(self): + "Prepend dot if CU_PREDOT is set." + + if self.hasFlag(CU_PREDOT): + self.cu = re.sub("^[^\.][\S]*", ".\g<0>", self.cu) + return self + else: + return self.stripAheadDot() + + def appendDot(self): + "Append dot if CU_ENDDOT is set." + + if self.hasFlag(CU_ENDDOT): + self.cu = re.sub("[\S]*[^\.]$", "\g<0>.", self.cu) + return self + + def appendTitlo(self): + """Append "titlo" unless CU_NOTITLO is set.""" + + if not self.hasFlag(CU_NOTITLO): + result = re.subn( + "([\S]+)(?{0}\g<2>".format(cu_titlo), + self.cu, + ) + self.cu = result[0] if result[1] > 0 else self.cu + cu_titlo + return self + + def swapDigits(input): + """Swap digits in 11-19 unless "і" is "thousand"-marked.""" + + result = re.sub( + "(?\g<1>", + input, + ) + return result + + def processDigit(input, registry=0, multiplier=0): + "Convert the Arabic digit to a CU numeral." + + return ( + cu_thousand * multiplier + cu_dict[10 * registry + input] if input else "" + ) + + def _processNumberPlain(input, registry=0, result=""): + "Process the Arabic number per digit." + # @registry is current registry + + if input: + result = ( + CUNumber.processDigit(input % 10, registry % 3, registry // 3) + result ) - return sub_result - - -def _to_cu_number_plain(input, registry=0, result="", flags=0): - """Process an arabic number per digit.""" - # @index is current registry - - sub_result = ( - _to_cu_digit(input % 10, registry % 3, registry // 3) + result - ) # Process rightmost digit - if input // 10: - # Iterate over each digit, increasing @registry index - return _to_cu_number_plain(input // 10, registry + 1, sub_result, flags) - else: - # Swap digits in 11-19 if "і" is not "҂"-marked - sub_result = re.sub( - "(? 1 and not re.match( - "[\S]*[%s\%s][\S]$" % (_cu_thousand, _cu_dot), sub_result - ): - sub_result = sub_result[: l - 1] + _cu_titlo + sub_result[l - 1 :] + + def _processNumberDelim(input, group=0, result=""): + "Process the Arabic number per groups of 3 digits." + # @index is current group of digits number (i.e. amount of multiplications of thousand) + + if input: + result = CUNumber.processGroup(input, group) + result + return CUNumber._processNumberDelim(input // 1000, group + 1, result) else: - sub_result += _cu_titlo # Else, append to the end + return result + + def processNumberPlain(self): - if _chflag(flags, CU_ENDDOT): # Append dot unless it's already there - sub_result = re.sub("[\s\S]*[^\.]$", "\g<0>.", sub_result) - if _chflag(flags, _CU_PREDOT): # Prepend dot unless it's already there - sub_result = re.sub("^[^\.][\s\S]*", ".\g<0>", sub_result) - else: # Remove leftover leftmost dot from CU_DELIMDOT - sub_result = re.sub("^\.([\s\S]*)", "\g<1>", sub_result) + self.cu = CUNumber._processNumberPlain(self.arabic) + return self - return sub_result + def processNumberDelim(self): + self.cu = CUNumber._processNumberDelim(self.arabic) + return self -def _digits_to_arab(input, group=0): - """Process CU numerals.""" - # DELIM MODE: @group is current hundred group + def convert(self): + "Convert the Arabic number to CU." + + if self.arabic <= 0: + return self + elif self.arabic < 1001 or self.hasFlag(CU_PLAIN): + self.processNumberPlain() + else: + self.processNumberDelim() + return self.stripDelimDots().prependDot().appendDot().appendTitlo() - # Swap digits in numbers 11-19 - input = re.sub(_cu_swap_regex[0], _cu_swap_regex[1], input) - subtotal = multiplier = 0 - for k in input: - if k == _cu_thousand: - multiplier += 1 - continue - index = _cu_dict.index(k) # Find current digit in dictionary +class ArabicNumber: + def __init__(self, input): + self.cu = input + self.arabic = None + self.prepare() + + def get(self): + return self.arabic + + def prepare(self): + "Prepare the CU number for conversion." + + self.cu = re.sub( + "[{0}\.]".format(cu_titlo), "", self.cu + ) # Strip ҃"҃ " and dots + self.cu = str.strip(self.cu) + self.cu = str.lower(self.cu) + + def processDigit(input, registry=0): + "Convert the CU numeral to an arabic digit." + print(input) + index = cu_dict.index(input) # Find current digit in dictionary number = index % 10 # Digit registry = index // 10 # Digit registry - subtotal += number * pow(10, registry) # Resulting number + return number * pow(10, registry) # Resulting number - # Multiply result by 1000 - times "҂" marks or current group - return subtotal * pow(1000, max(multiplier, group)) + def processGroup(input, group=0): + "Process the group of CU numerals." + subtotal = multiplier = 0 + for k in input: + if k == cu_thousand: + multiplier += 1 + continue + subtotal += ArabicNumber.processDigit(k) -def _to_arab_number(input, flags=0): - """Process a CU number.""" + # Multiply result by 1000 - times "҂" marks or current group + return subtotal * pow(1000, max(multiplier, group)) - sub_result = input - hundreds = [] + def prepareGroups(input, regex): + "Prepare CU digit groups for conversion." - if _chflag(flags, CU_PLAIN): - hundreds = re.split("%s" % _cu_plain_regex, sub_result) - else: - hundreds = re.split("%s" % _cu_delim_regex, sub_result) + groups = re.split(regex, input) - while hundreds.count(""): # Purge empty strs from collection - hundreds.remove("") - hundreds.reverse() + while groups.count(""): # Purge empty strs from collection + groups.remove("") + groups.reverse() + return groups - result = 0 - if _chflag(flags, CU_PLAIN): - for k in hundreds: - result += _digits_to_arab(k) - else: - for i, k in enumerate(hundreds): - result += _digits_to_arab(k, i) + def _processNumberPlain(input): + "Process the CU number per digit." - return result + groups = ArabicNumber.prepareGroups(input, cu_plain_regex) + result = 0 + for k in groups: + result += ArabicNumber.processGroup(k) + return result -def _prepare(input, flags=0): - """Prepare a CU number for conversion.""" + def _processNumberDelim(input): + "Process the CU number per groups of 1-3 digits." - input = re.sub("[%s\.]" % _cu_titlo, "", input) # Strip ҃"҃ " and dots - input = str.lower(str.strip(input)) # Trim and lowercase + groups = ArabicNumber.prepareGroups(input, cu_delim_regex) - if re.fullmatch("%s+" % _cu_plain_regex, input): - return _to_arab_number(input, flags=CU_PLAIN) - elif re.fullmatch("%s+" % _cu_delim_regex, input): - return _to_arab_number(input) - else: - raise ValueError( - "String does not match any pattern for Cyrillic numeral system number" - ) + result = 0 + for i, k in enumerate(groups): + result += ArabicNumber.processGroup(k, i) + return result + + def processNumberPlain(self): + self.arabic = ArabicNumber._processNumberPlain(self.cu) + return self + + def processNumberDelim(self): + self.arabic = ArabicNumber._processNumberDelim(self.cu) + return self + + def convert(self): + "Choose the CU number to Arabic." + + if not self.cu: + return self + if re.fullmatch("{0}+".format(cu_plain_regex), self.cu): + return self.processNumberPlain() + elif re.fullmatch("{0}+".format(cu_delim_regex), self.cu): + return self.processNumberDelim() + else: + return self def to_cu(input, flags=0): @@ -213,12 +273,9 @@ def to_cu(input, flags=0): Requires a non-zero integer. """ - t = type(input) - if t != int: - raise TypeError("Non-zero integer required, got %s" % t) - elif input <= 0: - raise ValueError("Non-zero integer required") - return _to_cu_number(input, flags) + if isinstance(input, int): + return CUNumber(input, flags).convert().get() + return None def to_arab(input, flags=0): @@ -228,9 +285,6 @@ def to_arab(input, flags=0): Requires a non-empty string. """ - t = type(input) - if t != str: - raise TypeError("String required, got %s" % t) - elif not input: - raise ValueError("Non-empty string required") - return _prepare(input) + if isinstance(input, str): + return ArabicNumber(input).convert().get() + return None diff --git a/setup.cfg b/setup.cfg index e1e3bdf..1352429 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = cu-numbers -version = 1.3.0 +version = 1.3.1 author = Andrei Shur author_email = amshoor@gmail.com description = Cyrillic numeral system numbers conversion diff --git a/tests/test_cunumbers.py b/tests/test_cunumbers.py index a252fd8..614a011 100644 --- a/tests/test_cunumbers.py +++ b/tests/test_cunumbers.py @@ -1,24 +1,24 @@ # -*- coding: UTF-8 -*- import unittest -from cunumbers import * +from cunumbers.cunumbers import * class ToCUPlainTestCase(unittest.TestCase): - def test_to_cu_digits(self): + def testToCUDigits(self): self.assertEqual(to_cu(1), "а҃") self.assertEqual(to_cu(9), "ѳ҃") - def test_to_cu_tens(self): + def testToCUTens(self): self.assertEqual(to_cu(10), "і҃") self.assertEqual(to_cu(18), "и҃і") self.assertEqual(to_cu(22), "к҃в") - def test_to_cu_hundreds(self): + def testToCUHundreds(self): self.assertEqual(to_cu(100), "р҃") self.assertEqual(to_cu(207), "с҃з") self.assertEqual(to_cu(333), "тл҃г") - def test_to_cu_thousand(self): + def testToCUThousand(self): self.assertEqual(to_cu(1000), "҂а҃") self.assertEqual(to_cu(1006, CU_PLAIN), "҂а҃ѕ") self.assertEqual(to_cu(1010, CU_PLAIN), "҂а҃і") @@ -26,7 +26,7 @@ def test_to_cu_thousand(self): self.assertEqual(to_cu(1444), "҂аум҃д") self.assertEqual(to_cu(11000, CU_PLAIN), "҂і҂а҃") - def test_to_cu_big(self): + def testToCUBig(self): self.assertEqual(to_cu(10001010001, CU_PLAIN), "҂҂҂і҂҂а҂і҃а") self.assertEqual(to_cu(50000000000, CU_PLAIN), "҂҂҂н҃") self.assertEqual(to_cu(60000070000, CU_PLAIN), "҂҂҂ѯ҂ѻ҃") @@ -34,11 +34,11 @@ def test_to_cu_big(self): class ToCUDelimTestCase(unittest.TestCase): - def test_to_cu_delim_thousand(self): + def testToCUDelimThousand(self): self.assertEqual(to_cu(1010), "҂а.і҃") self.assertEqual(to_cu(11000), "҂а҃і") - def test_to_cu_delim_big(self): + def testToCUDelimBig(self): self.assertEqual(to_cu(10001010001), "҂҂҂і҂҂а҂і҃а") self.assertEqual(to_cu(50000000000), "҂҂҂н҃") self.assertEqual(to_cu(60000070000), "҂҂҂ѯ҂ѻ҃") @@ -46,88 +46,88 @@ def test_to_cu_delim_big(self): class ToCUFlagsTestCase(unittest.TestCase): - def test_to_cu_notitlo(self): + def testToCUNotitlo(self): self.assertEqual(to_cu(1, CU_NOTITLO), "а") self.assertEqual(to_cu(11000, CU_PLAIN + CU_NOTITLO), "҂і҂а") - def test_to_cu_enddot(self): + def testToCUEnddot(self): self.assertEqual(to_cu(1, CU_ENDDOT), "а҃.") - def test_to_cu_wrapdot(self): + def testToCUWrapdot(self): self.assertEqual(to_cu(1, CU_WRAPDOT), ".а҃.") - def test_to_cu_delimdot(self): + def testToCUDelimdot(self): self.assertEqual(to_cu(1001, CU_DELIMDOT), "҂а.а҃") self.assertEqual(to_cu(1010, CU_DELIMDOT), "҂а.і҃") self.assertEqual(to_cu(11000, CU_DELIMDOT), "҂а҃і") self.assertEqual(to_cu(111111111, CU_DELIMDOT), "҂҂раі.҂раі.ра҃і") - def test_to_cu_alldot(self): + def testToCUAlldot(self): self.assertEqual(to_cu(1001, CU_ALLDOT), ".҂а.а҃.") - def test_to_cu_dotscustom(self): + def testToCUDotscustom(self): self.assertEqual(to_cu(1001, CU_ENDDOT + CU_DELIMDOT), "҂а.а҃.") class ToArabDelimTestCase(unittest.TestCase): - def test_to_arab_digits(self): + def testToArabDigits(self): self.assertEqual(1, to_arab("а҃")) self.assertEqual(9, to_arab("ѳ")) - def test_to_arab_tens(self): + def testToArabTens(self): self.assertEqual(10, to_arab("і҃")) self.assertEqual(18, to_arab("и҃і")) self.assertEqual(22, to_arab("к҃в")) - def test_to_arab_hundreds(self): + def testToArabHundreds(self): self.assertEqual(100, to_arab("р҃")) self.assertEqual(207, to_arab("с҃з")) self.assertEqual(333, to_arab("тл҃г")) - def test_to_arab_thousands(self): + def testToArabThousands(self): self.assertEqual(1000, to_arab("҂а҃")) self.assertEqual(1006, to_arab("҂а҃ѕ")) self.assertEqual(1015, to_arab("҂ає҃і")) self.assertEqual(1444, to_arab("҂аум҃д")) - def test_to_arab_big(self): + def testToArabBig(self): self.assertEqual(10001010001, to_arab("҂҂҂і҂҂а҂і҃а")) self.assertEqual(50000000000, to_arab("҂҂҂н҃")) self.assertEqual(60000070000, to_arab("҂҂҂ѯ҂ѻ҃")) - def test_to_arab_no_tsnd(self): + def testToArabNoTsnd(self): self.assertEqual(80500690700, to_arab("пфхч҃ѱ")) - def test_to_arab_notitlo(self): + def testToArabNotitlo(self): self.assertEqual(1, to_arab("а")) - def test_to_arab_spaced(self): + def testToArabSpaced(self): self.assertEqual(1, to_arab("а҃ ")) - def test_to_arab_uppercase(self): + def testToArabUppercase(self): self.assertEqual(1, to_arab("А҃")) - def test_to_arab_mixed(self): + def testToArabMixed(self): self.assertEqual(2021, to_arab(" вКА")) class ToArabPlainTestCase(unittest.TestCase): - def test_to_arab_plain_big(self): + def testToArabPlainBig(self): self.assertEqual(11000, to_arab("҂і҂а")) self.assertEqual(111111111, to_arab("҂҂р҂҂і҂҂а҂р҂і҂ара҃і")) class ErrorTestCase(unittest.TestCase): - def test_to_cu_error(self): - self.assertRaises(TypeError, to_cu, "String") - self.assertRaises(TypeError, to_cu, 9.75) - self.assertRaises(ValueError, to_cu, 0) - self.assertRaises(ValueError, to_cu, -69) - - def test_to_arab_error(self): - self.assertRaises(TypeError, to_arab, 420) - self.assertRaises(ValueError, to_arab, "") - self.assertRaises(ValueError, to_arab, "A113") + def testToCUError(self): + self.assertEqual(None, to_cu("String")) + self.assertEqual(None, to_cu(9.75)) + self.assertEqual(None, to_cu(0)) + self.assertEqual(None, to_cu(-69)) + + def testToArabError(self): + self.assertEqual(None, to_arab(420)) + self.assertEqual(None, to_arab("")) + self.assertEqual(None, to_arab("A113")) if __name__ == "__main__":