diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9d8d7a3..3b77ef0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,11 @@ 🌏 English [Русский](./CHANGELOG.ru.md) +## 2.0.0 + +- Refactoring +- Interface overhaul + ## 1.0.0 diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index 2703993..ded9aaf 100644 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -2,6 +2,11 @@ 🌏 [English](./CHANGELOG.md) Русский +## 2.0.0 + +- Рефакторинг +- Переработка интерфейсов + ## 1.0.0 diff --git a/docs/README.md b/docs/README.md index 460ee1a..1006c75 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,36 +26,36 @@ See [Introduction](./INTRODUCTION.md) to learn about Cyrillic numeral system. ## Usage - import omninumeric.cyrillic as CU + from omninumeric import cyrillic # Convert a number into Cyrillic # Requires non-zero int, returns str - a = CU.ArabicNumber(1).convert() + a = cyrillic.write(1) # Convert a Cyrillic number to Arabic # Requires non-empty str, returns int - b = CU.CyrillicNumber("а҃").convert() + b = cyrillic.read("а҃") "Delimiter" and "plain" style numbers are supported both for reading and writing, "plain" style is used by default for writing. When writing into Cyrillic, several falgs can be used: - # CU_DELIM flag sets conversion to "delimeter" style + # DELIM flag sets conversion to "delimeter" style - c = cu.to_alphabetic(111111, CU_DELIM) + c = cyrillic.write(111111, cyrillic.DELIM) - # CU_NOTITLO flag omits "titlo" decorator + # NOTITLO flag omits "titlo" decorator - d = cu.to_alphabetic(11000, CU_DELIM | CU_NOTITLO) + d = cyrillic.write(11000, cyrillic.DELIM | cyrillic.NOTITLO) # Following flags control dot styling: # - # CU_ENDDOT - append dot at the end - # CU_WRAPDOT - append dot at both ends - # CU_DELIMDOT - add dot separator between digit groups. Sets conversion to "delim" style - # CU_ALLDOT - combine CU_WRAPDOT and CU_DELIMDOT + # ENDDOT - append dot at the end + # WRAPDOT - append dot at both ends + # DELIMDOT - add dot separator between digit groups. Sets conversion to "delim" style + # ALLDOT - combine WRAPDOT and DELIMDOT ## Contributing diff --git a/docs/README.ru.md b/docs/README.ru.md index 632688f..0fd1313 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -26,36 +26,36 @@ ## Использование - import omninumeric.cyrillic as cu + from omninumeric import cyrillic # Преобразовать арабское число в церковнославянское # Принимает ненулевой int, возвращает str - a = CU.ArabicNumber(1).convert() + a = cyrillic.write(1) # Преобразовать церковнославянское число в арабское # Принимает непустой str, возвращает int - b = CU.CyrillicNumber("а҃").convert() + b = cyrillic.read("а҃") В обоих направлениях поддерживаются варианты записи "сплошной" и "по группам", "сплошная" запись используется по умолчанию. При записи в ЦСЯ возможно использование слеедующих флагов: - # CU_DELIM устанавливает вариант записи в ЦСЯ "по группам" + # DELIM устанавливает вариант записи в ЦСЯ "по группам" - c = cu.to_alphabetic(111111, CU_DELIM) + c = cyrillic.write(111111, cyrillic.DELIM) - # CU_NOTITLO опускает вывод знака "титло" + # NOTITLO опускает вывод знака "титло" - d = cu.to_alphabetic(11000, CU_DELIM | CU_NOTITLO) + d = cyrillic.write(11000, cyrillic.DELIM | cyrillic.NOTITLO) # Следующие флаги управляют декорированием точками: # - # CU_ENDDOT - выводит точку в конце - # CU_WRAPDOT - выводит точки с обеих сторон - # CU_DELIMDOT - выводит точки-разделители разрядов. Устанавливает вариант записи "по группам" - # CU_ALLDOT - комбинация флагов CU_WRAPDOT и CU_DELIMDOT + # ENDDOT - выводит точку в конце + # WRAPDOT - выводит точки с обеих сторон + # DELIMDOT - выводит точки-разделители разрядов. Устанавливает вариант записи "по группам" + # ALLDOT - комбинация флагов WRAPDOT и DELIMDOT ## Принять участие diff --git a/omninumeric/__init__.py b/omninumeric/__init__.py index 00b16dd..67a4f68 100644 --- a/omninumeric/__init__.py +++ b/omninumeric/__init__.py @@ -1,7 +1,3 @@ -from .omninumeric import ( - Dictionary, - IntNumberConverter, - StrNumberConverter, -) +from .omninumeric import * -__all__ = ["Dictionary", "IntNumberConverter", "StrNumberConverter"] +__all__ = ["Dictionary", "IntConverter", "StrConverter"] diff --git a/omninumeric/cyrillic/__init__.py b/omninumeric/cyrillic/__init__.py index 183c143..bc82b84 100644 --- a/omninumeric/cyrillic/__init__.py +++ b/omninumeric/cyrillic/__init__.py @@ -1,27 +1,15 @@ -from .cyrillic import ( - CU_PLAIN, - CU_DELIM, - CU_NOTITLO, - CU_ENDDOT, - CU_DELIMDOT, - CU_WRAPDOT, - CU_ALLDOT, - ArabicNumber, - CyrillicNumber, - to_cu, - to_arab, -) +from .cyrillic import * __all__ = [ - "ArabicNumber", - "CyrillicNumber", - "to_cu", - "to_arab", - "CU_PLAIN", - "CU_DELIM", - "CU_NOTITLO", - "CU_ENDDOT", - "CU_DELIMDOT", - "CU_WRAPDOT", - "CU_ALLDOT", + "PLAIN", + "DELIM", + "NOTITLO", + "ENDDOT", + "PREDOT", + "DOT", + "DELIMDOT", + "WRAPDOT", + "ALLDOT", + "read", + "write", ] diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 7f83149..2304d02 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -4,21 +4,21 @@ "This module provides tools for reading and writing numbers in Cyrillic numeral system." import re -from omninumeric.greek import * +from omninumeric import greek -CU_PLAIN = PLAIN # Write in plain style flag -CU_DELIM = DELIM # Read/write in delim style flag -CU_NOTITLO = 0b10 # DO NOT append titlo flag -CU_ENDDOT = 0b100 # Append dot flag -CU_PREDOT = 0b1000 # Prepend dot flag -CU_DOT = 0b10000 # Delimeter dots flag -CU_DELIMDOT = CU_DOT | CU_DELIM # Delimeter dots flag (forces delim style) -CU_WRAPDOT = CU_ENDDOT | CU_PREDOT # Wrap in dots flag -CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots flag +PLAIN = greek.PLAIN # Write in plain style flag +DELIM = greek.DELIM # Read/write in delim style flag +NOTITLO = 0b10 # DO NOT append titlo flag +ENDDOT = 0b100 # Append dot flag +PREDOT = 0b1000 # Prepend dot flag +DOT = 0b10000 # Delimeter dots flag +DELIMDOT = DOT | DELIM # Delimeter dots flag (forces delim style) +WRAPDOT = ENDDOT | PREDOT # Wrap in dots flag +ALLDOT = ENDDOT | PREDOT | DELIMDOT # Wrapper and delimeter dots flag -class _CyrillicDictionary(DictionaryGreek): +class Dictionary(greek.Dictionary): "Cyrillic numerals ditcionary." а = 1 @@ -48,164 +48,183 @@ class _CyrillicDictionary(DictionaryGreek): ѱ = 700 ѿ = 800 ц = 900 + + +class Const: THOUSAND = "҂" # "Thousand" mark TITLO = "҃" # "Titlo" decorator - DOT = "." # Dot decorator + DELIMETER = "." # Dot decorator -class ArabicNumber(IntNumberConverterGreek): +class IntConverter(greek.IntConverter): "Number converter into Cyrillic numeral system." - _dict = _CyrillicDictionary + dict_ = Dictionary + const = Const - def _ambiguityCheck(self, cond, flag): - if cond: + def ambiguityCheck(self, cond, flag): + "Force delimeter for ambiguous numbers (i.e. ҂а҃і and ҂а.і҃)." + if self.hasFlag(cond): try: - if (self._groups[0] // 10 % 10 == 1) and ( - self._groups[1] // 10 % 10 == 0 + if (self.groups[0] // 10 % 10 == 1) and ( + self.groups[1] // 10 % 10 == 0 ): - self._flags = self._flags | flag + self.flags = self.flags | flag finally: return self else: return self - def _swapDigits(self): + def swapDigits(self): "Swap digits for values 11-19 (unless separated)." - for i, k in enumerate(self._groups): + for i, k in enumerate(self.groups): - self._groups[i] = re.sub( - "({0})([{1}])".format(self._dict.get(10), self._dict.digits()), + self.groups[i] = re.sub( + "({0})([{1}])".format(self.dict_.get(10), self.dict_.digits()), "\g<2>\g<1>", - self._groups[i], + self.groups[i], ) return self - def _appendTitlo(self, cond): + def appendTitlo(self, cond): 'Apply "titlo" decorator unless appropriate flag is set.' - if not cond: + if not self.hasFlag(cond): result = re.subn( "([\S]+)(?{0}\g<2>".format(self._dict.get("TITLO")), - self._target, + "\g<1>{0}\g<2>".format(self.const.TITLO), + self.target, ) - self._target = ( + self.target = ( result[0] if result[1] > 0 - else "{0}{1}".format(self._target, self._dict.get("TITLO")) + else "{0}{1}".format(self.target, self.const.TITLO) ) return self - def _delimDots(self, cond): + def delimDots(self, cond): "Insert dots between numeral groups if appropriate flag is set." - if cond: - for i, k in enumerate(self._groups[1:]): - self._groups[i + 1] = "{0}{1}".format(k, self._dict.get("DOT")) + if self.hasFlag(cond): + for i, k in enumerate(self.groups[1:]): + self.groups[i + 1] = "{0}{1}".format(k, self.const.DELIMETER) return self - def _wrapDot(self, cond_a, cond_b): + def wrapDot(self, cond_a, cond_b): "Prepend and/or append a dot if appropriate flags are set." - self._target = "{0}{1}{2}".format( - self._dict.get("DOT") if cond_a else "", - self._target, - self._dict.get("DOT") if cond_b else "", + self.target = "{0}{1}{2}".format( + self.const.DELIMETER if self.hasFlag(cond_a) else "", + self.target, + self.const.DELIMETER if self.hasFlag(cond_b) else "", ) return self - def convert(self): - """ - Convert into Cyrillic numeral system. Uses plain style by default. + def appendThousandMarks(self, cond): + return super().appendThousandMarks(self.hasFlag(cond), self.const.THOUSAND) - Requires a non-zero integer. - """ + def convert(self): + "Convert into Cyrillic numeral system. Uses plain style by default." return ( - self._validate() - ._breakIntoGroups() - ._ambiguityCheck(self._hasFlag(CU_DELIM), CU_DOT) - ._translateGroups() - ._appendThousandMarks(self._hasFlag(CU_DELIM)) - ._purgeEmptyGroups() - ._swapDigits() - ._delimDots(self._hasFlag(CU_DOT)) - ._build() - ._appendTitlo(self._hasFlag(CU_NOTITLO)) - ._wrapDot(self._hasFlag(CU_PREDOT), self._hasFlag(CU_ENDDOT)) - ._get() + self.validate() + .breakIntoGroups() + .ambiguityCheck(DELIM, DOT) + .translateGroups() + .appendThousandMarks(DELIM) + .purgeEmptyGroups() + .swapDigits() + .delimDots(DOT) + .build() + .appendTitlo(NOTITLO) + .wrapDot(PREDOT, ENDDOT) + .get() ) -class CyrillicNumber(StrNumberConverterGreek): +class StrConverter(greek.StrConverter): "Number converter from Cyrillic numeral system." - _dict = _CyrillicDictionary + dict_ = Dictionary + const = Const - _regex = "({0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?))".format( - _dict.get("THOUSAND"), - _dict.hundreds(), - _dict.tens(2), - _dict.digits(), - _dict.get(10), + regex = "({0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?))".format( + const.THOUSAND, + dict_.hundreds(), + dict_.tens(2), + dict_.digits(), + dict_.get(10), ) # Regular expression for typical Cyrillic numeral system number - def _prepare(self): + def prepare(self): "Prepare source number for conversion." - super()._prepare() - self._source = re.sub( - "[{0}\{1}]".format(self._dict.get("TITLO"), self._dict.get("DOT")), + super().prepare() + self.source = re.sub( + "[{0}\{1}]".format(self.const.TITLO, self.const.DELIMETER), "", - self._source, + self.source, ) # Strip ҃decorators return self - def _validate(self): + def validate(self): "Validate that source number is a non-empty string and matches the pattern for Cyrillic numeral system numbers." - super()._validate() - if not re.fullmatch("{0}+".format(self._regex), self._source): + super().validate() + if not re.fullmatch("{0}+".format(self.regex), self.source): raise ValueError( "String does not match any pattern for Cyrillic numeral system numbers" ) return self - def convert(self): - """ - Convert from Cyrillic numeral system. + @classmethod + def calculateMultiplier(cls, index, group): + return super().calculateMultiplier(index, group, cls.const.THOUSAND) - Requires a non-empty string. - """ + def breakIntoGroups(self): + return super().breakIntoGroups(self.regex) + + def translateGroups(self): + return super().translateGroups(self.const.THOUSAND) + + def convert(self): + "Convert from Cyrillic numeral system." return ( - self._prepare() - ._validate() - ._breakIntoGroups(self._regex) - ._purgeEmptyGroups() - ._translateGroups() - ._build() - ._get() + self.prepare() + .validate() + .breakIntoGroups() + .purgeEmptyGroups() + .translateGroups() + .build() + .get() ) -def to_cu(integer, flags=0): - "Deprecated. Use ArabicNumber().convert() instead." +def write(number, flags=0): + """ + Convert into Cyrillic numeral system. Uses plain style by default. + + Requires a non-zero integer. + """ + + return IntConverter(number, flags).convert() - return ArabicNumber(integer, flags).convert() +def read(number, flags=0): + """ + Convert from Cyrillic numeral system. -def to_arab(alphabetic, flags=0): - "Deprecated. Use CyrillicNumber().convert() instead." + Requires a non-empty string. + """ - return CyrillicNumber(alphabetic).convert() + return StrConverter(number, flags).convert() diff --git a/omninumeric/greek/__init__.py b/omninumeric/greek/__init__.py index 57c6f1b..f499ba8 100644 --- a/omninumeric/greek/__init__.py +++ b/omninumeric/greek/__init__.py @@ -1,15 +1,3 @@ -from .greek import ( - PLAIN, - DELIM, - DictionaryGreek, - IntNumberConverterGreek, - StrNumberConverterGreek, -) +from .greek import * -__all__ = [ - "PLAIN", - "DELIM", - "DictionaryGreek", - "IntNumberConverterGreek", - "StrNumberConverterGreek", -] +__all__ = ["PLAIN", "DELIM", "Dictionary", "IntConverter", "StrConverter"] diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index d60f09f..b93a540 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -4,18 +4,14 @@ import re -from omninumeric import ( - Dictionary, - IntNumberConverter, - StrNumberConverter, -) +import omninumeric PLAIN = 0 # Write in plain style flag DELIM = 0b1 # Read/write in delim style flag -class DictionaryGreek(Dictionary): +class Dictionary(omninumeric.Dictionary): """ ABC for Greek-type alphabetic numeral systems ditcionaries. @@ -23,7 +19,7 @@ class DictionaryGreek(Dictionary): """ @classmethod - def _getmany(cls, start=1, end=10, step=1): + def getmany(cls, start=1, end=10, step=1): """ Look a range of numerals up in dictionary. @@ -45,7 +41,7 @@ def digits(cls, start=1, end=9): @start - starting numeral value (i.e. 5 for range of 5, 6, 7...) @end - ending numeral value (i.e. 5 for range of ...3, 4, 5) """ - return cls._getmany(start, end, 1) + return cls.getmany(start, end, 1) @classmethod def tens(cls, start=1, end=9): @@ -55,7 +51,7 @@ def tens(cls, start=1, end=9): @start - starting numeral value (i.e. 5 for range of 50, 60, 70...) @end - ending numeral value (i.e. 5 for range of ...30, 40, 50) """ - return cls._getmany(start, end, 10) + return cls.getmany(start, end, 10) @classmethod def hundreds(cls, start=1, end=9): @@ -65,65 +61,63 @@ def hundreds(cls, start=1, end=9): @start - starting numeral value (i.e. 5 for range of 500, 600, 700...) @end - ending numeral value (i.e. 5 for range of ...300, 400, 500) """ - return cls._getmany(start, end, 100) + return cls.getmany(start, end, 100) -class IntNumberConverterGreek(IntNumberConverter): +class IntConverter(omninumeric.IntConverter): """ ABC for number conversion into Greek-type alphabetic numeral systems. Derive from this class to define converters into Greek-type alphabetic numeral systems. """ - def _appendThousandMarks(self, cond): + def appendThousandMarks(self, cond, thousand): "Append thousand marks according to chosen style (plain or delimeter)." - for i, k in enumerate(self._groups): + for i, k in enumerate(self.groups): if k: - if cond: - result = "{0}{1}".format(self._dict.get("THOUSAND") * i, k) + if self.hasFlag(cond): + result = "{0}{1}".format(thousand * i, k) else: result = "" for l in k: - result = "{0}{1}{2}".format( - result, self._dict.get("THOUSAND") * i, l - ) + result = "{0}{1}{2}".format(result, thousand * i, l) - self._groups[i] = result + self.groups[i] = result return self - def _translateGroups(self): + def translateGroups(self): "Translate groups of numerals one by one." - for i, k in enumerate(self._groups): + for i, k in enumerate(self.groups): result = "" index = 0 while k > 0: - result = self._getNumeral(k % 10 * pow(10, index)) + result + result = self.getNumeral(k % 10 * pow(10, index)) + result index = index + 1 k = k // 10 - self._groups[i] = result + self.groups[i] = result return self - def _breakIntoGroups(self): + def breakIntoGroups(self): "Break source number into groups of 3 numerals." - while self._source > 0: - self._groups.append(self._source % 1000) - self._source = self._source // 1000 + while self.source > 0: + self.groups.append(self.source % 1000) + self.source = self.source // 1000 return self -class StrNumberConverterGreek(StrNumberConverter): +class StrConverter(omninumeric.StrConverter): """ ABC for number conversion from Greek-type alphabetic numeral systems. @@ -131,37 +125,35 @@ class StrNumberConverterGreek(StrNumberConverter): """ @classmethod - def _calculateMultiplier(cls, index, group): + def calculateMultiplier(cls, index, group, thousand): 'Calculate multiplier for a numerals group, according to group index or "thousand" marks present in the group.' multiplier = ( - re.match("({0}*)".format(cls._dict.get("THOUSAND")), group) - .groups()[0] - .count(cls._dict.get("THOUSAND")) + re.match("({0}*)".format(thousand), group).groups()[0].count(thousand) ) # Count trailing thousand marks in the group multiplier = pow(1000, multiplier if multiplier else index) # Use thousand marks if present, otherwise use group index return multiplier - def _translateGroups(self): + def translateGroups(self, thousand): "Translate groups of numerals one by one." - for i, k in enumerate(self._groups): + for i, k in enumerate(self.groups): total = 0 # Current group total value - multiplier = self._calculateMultiplier(i, k) - k = re.sub(self._dict.get("THOUSAND"), "", k) # Strip thousand marks + multiplier = self.calculateMultiplier(i, k) + k = re.sub(thousand, "", k) # Strip thousand marks for l in k: - total += self._getNumeral(l) + total += self.getNumeral(l) - self._groups[i] = total * multiplier + self.groups[i] = total * multiplier return self - def _breakIntoGroups(self, regex=""): + def breakIntoGroups(self, regex=""): "Break source number in groups of 1-3 numerals." - self._groups = re.split(regex, self._source) # Break into groups - self._groups.reverse() # Reverse groups (to ascending order) + self.groups = re.split(regex, self.source) # Break into groups + self.groups.reverse() # Reverse groups (to ascending order) return self diff --git a/omninumeric/greek/old.py b/omninumeric/greek/old.py index b14c7ed..af2c25a 100644 --- a/omninumeric/greek/old.py +++ b/omninumeric/greek/old.py @@ -9,14 +9,10 @@ __all__ = ["ArabicNumber", "OldGreekNumber"] -from omninumeric import ( - StrNumberConverter, - IntNumberConverter, -) -from omninumeric.greek import * +from omninumeric import greek -class _OldGreekDictionary(DictionaryGreek): +class Dictionary(greek.Dictionary): "Old Greek numerals dictionary" α = 1 @@ -52,11 +48,9 @@ class _OldGreekDictionary(DictionaryGreek): DOT = "." # Dot decorator -class ArabicNumber(IntNumberConverterGreek): +class IntConverter(greek.IntConverter): "Number converter into Old Greek numeral system." - _dict = _OldGreekDictionary - def convert(self): """ Convert into Old Greek numeral system. Uses plain style by default. @@ -64,19 +58,19 @@ def convert(self): Requires a non-zero integer. """ return ( - self._breakIntoGroups() - ._translateGroups() - ._appendThousandMarks(self._hasFlag(DELIM)) - ._purgeEmptyGroups() - ._build() - ._get() + self.breakIntoGroups() + .translateGroups() + .appendThousandMarks(self.hasFlag(self.flag.DELIM)) + .purgeEmptyGroups() + .build() + .get() ) -class OldGreekNumber(StrNumberConverterGreek): +class StrConverter(greek.StrConverter): "Number converter from Old Greek numeral system." - _dict = _OldGreekDictionary + dict = Dictionary def convert(self): """ @@ -85,10 +79,4 @@ def convert(self): Requires a non-empty string. """ - return ( - self._breakIntoGroups() - ._purgeEmptyGroups() - ._translateGroups() - ._build() - ._get() - ) + return self.breakIntoGroups().purgeEmptyGroups().translateGroups().build().get() diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 48ce585..d442a8e 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -52,34 +52,35 @@ class NumberConverter: Derive from this class to define converters into and from alphabetic numeral systems. """ - _dict = NotImplemented + dict_ = NotImplemented + const = NotImplemented def __init__(self, source, target, flags=0): - self._source = source - self._target = target - self._flags = flags - self._groups = [] + self.source = source + self.target = target + self.flags = flags + self.groups = [] - def _hasFlag(self, flag): + def hasFlag(self, flag): "Check if a flag is set." - return self._flags & flag + return self.flags & flag # return False if self._flags & flag == 0 else True - def _get(self): + def get(self): "Return the converted number." - return self._target + return self.target - def _build(self): + def build(self): "Build the converted number from groups of numerals." - for k in self._groups: - self._target = k + self._target + for k in self.groups: + self.target = k + self.target return self @classmethod - def _getNumeral(cls, numeral, fallback): + def getNumeral(cls, numeral, fallback): """ Look a numeral up in dictionary. @@ -87,22 +88,20 @@ def _getNumeral(cls, numeral, fallback): @fallback - value to return if @numeral is not found """ - return cls._dict.get(numeral) or fallback + return cls.dict_.get(numeral) or fallback - def _purgeEmptyGroups(self): + def purgeEmptyGroups(self): "Remove empty groups from numeral groups collection." - print(self._groups) - while self._groups.count(""): - self._groups.remove("") # Purge empty groups - print(self._groups) + while self.groups.count(""): + self.groups.remove("") # Purge empty groups return self def convert(self): raise NotImplementedError -class IntNumberConverter(NumberConverter): +class IntConverter(NumberConverter): """ ABC for number conversion into alphabetic numeral systems. @@ -112,51 +111,51 @@ class IntNumberConverter(NumberConverter): def __init__(self, value, flags=0): super().__init__(value, "", flags) - def _validate(self): + def validate(self): "Validate that source number is a natural number." - isinstanceEx(self._source, int, "Integer required, got {0}") + isinstanceEx(self.source, int, "Integer required, got {0}") - if self._source <= 0: + if self.source <= 0: raise ValueError("Natural number required") return self @classmethod - def _getNumeral(cls, numeral): + def getNumeral(cls, numeral): "Get alphabetic digit for given value." - return super()._getNumeral(numeral, "") + return super().getNumeral(numeral, "") -class StrNumberConverter(NumberConverter): +class StrConverter(NumberConverter): """ ABC for number conversion from alphabetic numeral systems. - Derive from this class to define converters from ABS. + Derive from this class to define converters from alphabetic numeral systems. """ def __init__(self, alphabetic, flags=0): super().__init__(alphabetic, 0, flags) - def _validate(self): + def validate(self): "Validate that source number is a non-empty string." - isinstanceEx(self._source, str, "String required, got {0}") + isinstanceEx(self.source, str, "String required, got {0}") - if not self._source: + if not self.source: raise ValueError("Non-empty string required") return self - def _prepare(self): + def prepare(self): "Prepare source number for further operations." - self._source = str.lower(str.strip(self._source)) + self.source = str.lower(str.strip(self.source)) return self @classmethod - def _getNumeral(cls, numeral): + def getNumeral(cls, numeral): "Get value for given alphabetic digit." - return super()._getNumeral(numeral, 0) + return super().getNumeral(numeral, 0) diff --git a/setup.py b/setup.py index ec58b24..8b0b94f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="omninumeric", - version="1.0.0", + version="2.0.0", author="Andrei Shur", author_email="amshoor@gmail.com", description="Read and write numbers in alphabetic numeral systems", diff --git a/tests/test_cyrillic.py b/tests/test_cyrillic.py index a59a863..d040251 100644 --- a/tests/test_cyrillic.py +++ b/tests/test_cyrillic.py @@ -3,137 +3,137 @@ from omninumeric.cyrillic import * -class ToCUPlainTestCase(unittest.TestCase): - def testToCUDigits(self): - self.assertEqual(to_cu(1), "а҃") - self.assertEqual(to_cu(9), "ѳ҃") - - def testToCUTens(self): - self.assertEqual(to_cu(10), "і҃") - self.assertEqual(to_cu(18), "и҃і") - self.assertEqual(to_cu(22), "к҃в") - - def testToCUHundreds(self): - self.assertEqual(to_cu(100), "р҃") - self.assertEqual(to_cu(207), "с҃з") - self.assertEqual(to_cu(333), "тл҃г") - - def testToCUThousand(self): - self.assertEqual(to_cu(1000), "҂а҃") - self.assertEqual(to_cu(1006), "҂а҃ѕ") - self.assertEqual(to_cu(1010), "҂а҃і") - self.assertEqual(to_cu(1015), "҂ає҃і") - self.assertEqual(to_cu(1444), "҂аум҃д") - self.assertEqual(to_cu(11000), "҂і҂а҃") - - def testToCUBig(self): - self.assertEqual(to_cu(10001010001), "҂҂҂і҂҂а҂і҃а") - self.assertEqual(to_cu(50000000000), "҂҂҂н҃") - self.assertEqual(to_cu(60000070000), "҂҂҂ѯ҂ѻ҃") - self.assertEqual(to_cu(111111111), "҂҂р҂҂і҂҂а҂р҂і҂ара҃і") - - -class ToCUDelimTestCase(unittest.TestCase): - def testToCUDelimAmbiguity(self): - self.assertEqual(to_cu(1010, CU_DELIM), "҂а.і҃") - self.assertEqual(to_cu(11000, CU_DELIM), "҂а҃і") - self.assertEqual(to_cu(10010, CU_DELIM), "҂і҃і") - self.assertEqual(to_cu(110010, CU_DELIM), "҂рі҃і") - self.assertEqual(to_cu(100010, CU_DELIM), "҂р.і҃") - self.assertEqual(to_cu(110000, CU_DELIM), "҂р҃і") - self.assertEqual(to_cu(100011, CU_DELIM), "҂р.а҃і") - self.assertEqual(to_cu(111000, CU_DELIM), "҂ра҃і") - - def testToCUDelimBig(self): - self.assertEqual(to_cu(10001010001, CU_DELIM), "҂҂҂і҂҂а҂і҃а") - self.assertEqual(to_cu(50000000000, CU_DELIM), "҂҂҂н҃") - self.assertEqual(to_cu(60000070000, CU_DELIM), "҂҂҂ѯ҂ѻ҃") - self.assertEqual(to_cu(111111111, CU_DELIM), "҂҂раі҂раіра҃і") - - -class ToCUFlagsTestCase(unittest.TestCase): - def testToCUNotitlo(self): - self.assertEqual(to_cu(1, CU_NOTITLO), "а") - self.assertEqual(to_cu(11000, CU_NOTITLO), "҂і҂а") - - def testToCUEnddot(self): - self.assertEqual(to_cu(1, CU_ENDDOT), "а҃.") - - def testToCUWrapdot(self): - self.assertEqual(to_cu(1, CU_WRAPDOT), ".а҃.") - - 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 testToCUAlldot(self): - self.assertEqual(to_cu(1001, CU_ALLDOT), ".҂а.а҃.") - - def testToCUDotscustom(self): - self.assertEqual(to_cu(1001, CU_ENDDOT + CU_DELIMDOT), "҂а.а҃.") - - -class ToArabDelimTestCase(unittest.TestCase): - def testToArabDigits(self): - self.assertEqual(1, to_arab("а҃")) - self.assertEqual(9, to_arab("ѳ")) - - def testToArabTens(self): - self.assertEqual(10, to_arab("і҃")) - self.assertEqual(18, to_arab("и҃і")) - self.assertEqual(22, to_arab("к҃в")) - - def testToArabHundreds(self): - self.assertEqual(100, to_arab("р҃")) - self.assertEqual(207, to_arab("с҃з")) - self.assertEqual(333, to_arab("тл҃г")) - - def testToArabThousands(self): - self.assertEqual(1000, to_arab("҂а҃")) - self.assertEqual(1006, to_arab("҂а҃ѕ")) - self.assertEqual(1015, to_arab("҂ає҃і")) - self.assertEqual(1444, to_arab("҂аум҃д")) - - def testToArabBig(self): - self.assertEqual(10001010001, to_arab("҂҂҂і҂҂а҂і҃а")) - self.assertEqual(50000000000, to_arab("҂҂҂н҃")) - self.assertEqual(60000070000, to_arab("҂҂҂ѯ҂ѻ҃")) - - def testToArabNoTsnd(self): - self.assertEqual(80500690700, to_arab("пфхч҃ѱ")) - - def testToArabNotitlo(self): - self.assertEqual(1, to_arab("а")) +class WritePlainTestCase(unittest.TestCase): + def testWriteDigits(self): + self.assertEqual(write(1), "а҃") + self.assertEqual(write(9), "ѳ҃") + + def testWriteTens(self): + self.assertEqual(write(10), "і҃") + self.assertEqual(write(18), "и҃і") + self.assertEqual(write(22), "к҃в") + + def testWriteHundreds(self): + self.assertEqual(write(100), "р҃") + self.assertEqual(write(207), "с҃з") + self.assertEqual(write(333), "тл҃г") + + def testWriteThousand(self): + self.assertEqual(write(1000), "҂а҃") + self.assertEqual(write(1006), "҂а҃ѕ") + self.assertEqual(write(1010), "҂а҃і") + self.assertEqual(write(1015), "҂ає҃і") + self.assertEqual(write(1444), "҂аум҃д") + self.assertEqual(write(11000), "҂і҂а҃") + + def testWriteBig(self): + self.assertEqual(write(10001010001), "҂҂҂і҂҂а҂і҃а") + self.assertEqual(write(50000000000), "҂҂҂н҃") + self.assertEqual(write(60000070000), "҂҂҂ѯ҂ѻ҃") + self.assertEqual(write(111111111), "҂҂р҂҂і҂҂а҂р҂і҂ара҃і") + + +class WriteDelimTestCase(unittest.TestCase): + def testWriteDelimAmbiguity(self): + self.assertEqual(write(1010, DELIM), "҂а.і҃") + self.assertEqual(write(11000, DELIM), "҂а҃і") + self.assertEqual(write(10010, DELIM), "҂і҃і") + self.assertEqual(write(110010, DELIM), "҂рі҃і") + self.assertEqual(write(100010, DELIM), "҂р.і҃") + self.assertEqual(write(110000, DELIM), "҂р҃і") + self.assertEqual(write(100011, DELIM), "҂р.а҃і") + self.assertEqual(write(111000, DELIM), "҂ра҃і") + + def testWriteDelimBig(self): + self.assertEqual(write(10001010001, DELIM), "҂҂҂і҂҂а҂і҃а") + self.assertEqual(write(50000000000, DELIM), "҂҂҂н҃") + self.assertEqual(write(60000070000, DELIM), "҂҂҂ѯ҂ѻ҃") + self.assertEqual(write(111111111, DELIM), "҂҂раі҂раіра҃і") + + +class WriteFlagsTestCase(unittest.TestCase): + def testWriteNotitlo(self): + self.assertEqual(write(1, NOTITLO), "а") + self.assertEqual(write(11000, NOTITLO), "҂і҂а") + + def testWriteEnddot(self): + self.assertEqual(write(1, ENDDOT), "а҃.") + + def testWriteWrapdot(self): + self.assertEqual(write(1, WRAPDOT), ".а҃.") + + def testWriteDelimdot(self): + self.assertEqual(write(1001, DELIMDOT), "҂а.а҃") + self.assertEqual(write(1010, DELIMDOT), "҂а.і҃") + self.assertEqual(write(11000, DELIMDOT), "҂а҃і") + self.assertEqual(write(111111111, DELIMDOT), "҂҂раі.҂раі.ра҃і") + + def testWriteAlldot(self): + self.assertEqual(write(1001, ALLDOT), ".҂а.а҃.") + + def testWriteDotscustom(self): + self.assertEqual(write(1001, ENDDOT + DELIMDOT), "҂а.а҃.") + + +class ReadDelimTestCase(unittest.TestCase): + def testReadDigits(self): + self.assertEqual(1, read("а҃")) + self.assertEqual(9, read("ѳ")) + + def testReadTens(self): + self.assertEqual(10, read("і҃")) + self.assertEqual(18, read("и҃і")) + self.assertEqual(22, read("к҃в")) + + def testReadHundreds(self): + self.assertEqual(100, read("р҃")) + self.assertEqual(207, read("с҃з")) + self.assertEqual(333, read("тл҃г")) + + def testReadThousands(self): + self.assertEqual(1000, read("҂а҃")) + self.assertEqual(1006, read("҂а҃ѕ")) + self.assertEqual(1015, read("҂ає҃і")) + self.assertEqual(1444, read("҂аум҃д")) + + def testReadBig(self): + self.assertEqual(10001010001, read("҂҂҂і҂҂а҂і҃а")) + self.assertEqual(50000000000, read("҂҂҂н҃")) + self.assertEqual(60000070000, read("҂҂҂ѯ҂ѻ҃")) + + def testReadNoTsnd(self): + self.assertEqual(80500690700, read("пфхч҃ѱ")) + + def testReadNotitlo(self): + self.assertEqual(1, read("а")) - def testToArabSpaced(self): - self.assertEqual(1, to_arab("а҃ ")) + def testReadSpaced(self): + self.assertEqual(1, read("а҃ ")) - def testToArabUppercase(self): - self.assertEqual(1, to_arab("А҃")) + def testReadUppercase(self): + self.assertEqual(1, read("А҃")) - def testToArabMixed(self): - self.assertEqual(2021, to_arab(" вКА")) + def testReadMixed(self): + self.assertEqual(2021, read(" вКА")) -class ToArabPlainTestCase(unittest.TestCase): - def testToArabPlainBig(self): - self.assertEqual(11000, to_arab("҂і҂а")) - self.assertEqual(111111111, to_arab("҂҂р҂҂і҂҂а҂р҂і҂ара҃і")) +class ReadPlainTestCase(unittest.TestCase): + def testReadPlainBig(self): + self.assertEqual(11000, read("҂і҂а")) + self.assertEqual(111111111, read("҂҂р҂҂і҂҂а҂р҂і҂ара҃і")) class ErrorTestCase(unittest.TestCase): - def testToCUError(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 testToArabError(self): - self.assertRaises(TypeError, to_arab, 420) - self.assertRaises(ValueError, to_arab, "") - self.assertRaises(ValueError, to_arab, "A113") + def testWriteError(self): + self.assertRaises(TypeError, write, "String") + self.assertRaises(TypeError, write, 9.75) + self.assertRaises(ValueError, write, 0) + self.assertRaises(ValueError, write, -69) + + def testReadError(self): + self.assertRaises(TypeError, read, 420) + self.assertRaises(ValueError, read, "") + self.assertRaises(ValueError, read, "A113") if __name__ == "__main__":