From 2c97e618853973d2730bab2eb04e77d4789fe88b Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Mon, 5 Jul 2021 05:59:46 +0300 Subject: [PATCH 01/39] Refactor CU conversion --- cunumbers/cunumbers.py | 180 ++++++++++++++++++++++++----------------- 1 file changed, 108 insertions(+), 72 deletions(-) diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index dc4bbba..a53e29a 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -49,57 +49,54 @@ def __init__(self, input, flags=0): self.cu = "" self.arabic = input self.flags = flags + self.groups = [] self.prepare() def get(self): + "Return the CU number string representation." + return self.cu def prepare(self): + "Prepare the Arabic number for conversion." + if self.arabic <= 0: raise ValueError("Non-zero integer required") def hasFlag(self, flag): - """Check a flag.""" + "Check if a flag is set." return False if self.flags & flag == 0 else True - def stripDelimDots(self): - "Strip delimeter dots unless CU_DELIMDOT is set." + def build(self): + "Build the CU number from digit groups." - if not self.hasFlag(CU_DELIMDOT): - self.cu = re.sub( - "(\{0}(?!{1}$)|(?", self.cu) - return self + self.cu = ( + (cu_dot if self.hasFlag(CU_PREDOT) else "") + + self.cu + + (cu_dot if self.hasFlag(CU_ENDDOT) else "") + ) - def prependDot(self): - "Prepend dot if CU_PREDOT is set." + return self - if self.hasFlag(CU_PREDOT): - self.cu = re.sub("^[^\.][\S]*", ".\g<0>", self.cu) - return self - else: - return self.stripAheadDot() + def delimDots(self): + "Insert dots between digit groups if appropriate flag is set." - def appendDot(self): - "Append dot if CU_ENDDOT is set." + if self.hasFlag(CU_DELIMDOT): + for i, k in enumerate(self.groups[1:]): + self.groups[i + 1] = k + cu_dot - 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.""" + "Append titlo unless appropriate flag is set." if not self.hasFlag(CU_NOTITLO): result = re.subn( @@ -108,75 +105,114 @@ def appendTitlo(self): 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.""" + def appendThousandMarksDelim(input, index): + "Append thousand marks in delimeter style." + + if input: + return cu_thousand * index + input + else: + return "" + + def appendThousandMarksPlain(input, index): + "Append thousand marks in plain style." + + result = "" + + for i in input: + result = result + CUNumber.appendThousandMarksDelim(i, index) - result = re.sub( - "(?\g<1>", - input, - ) return result - def processDigit(input, registry=0, multiplier=0): - "Convert the Arabic digit to a Cyrillic numeral." + def appendThousandMarks(self): + "Append thousand marks according to chosen style (plain or delimeter)." - return ( - cu_thousand * multiplier + cu_dict[10 * registry + input] if input else "" + method = ( + CUNumber.appendThousandMarksPlain + if self.hasFlag(CU_PLAIN) + else CUNumber.appendThousandMarksDelim ) - def _processNumberPlain(input, registry=0, result=""): - "Process the Arabic number per digit." - # @registry is current registry + for i, k in enumerate(self.groups): - if input: - result = ( - CUNumber.processDigit(input % 10, registry % 3, registry // 3) + result + self.groups[i] = method(self.groups[i], i) + + return self + + def swapDigits(self): + "Swap digits in 11-19." + + for i, k in enumerate(self.groups): + + self.groups[i] = re.sub( + "({0})([{1}])".format(cu_tens[0], cu_digits), + "\g<2>\g<1>", + self.groups[i], ) - return CUNumber._processNumberPlain(input // 10, registry + 1, result) - else: - return CUNumber.swapDigits(result) - def processGroup(input, group=0): - "Process the group of 3 Arabic digits." + return self - input = input % 1000 - return ( - (cu_dot + cu_thousand * group + CUNumber._processNumberPlain(input)) - if input - else "" - ) + def purgeEmptyGroups(self): + "Remove empty groups from digit group collection." + + for i, k in enumerate(self.groups): + + if not k: + self.groups.pop(i) + + return self - 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) + def getDigit(input, index): + "Get CU digit for given Arabic digit." if input: - result = CUNumber.processGroup(input, group) + result - return CUNumber._processNumberDelim(input // 1000, group + 1, result) + return cu_dict[input + 10 * index] else: - return result + return "" - def processNumberPlain(self): + def translateGroups(self): + "Translate the Arabic number per group." + + for i, k in enumerate(self.groups): + + result = "" + index = 0 + + while k > 0: + result = CUNumber.getDigit(k % 10, index) + result + index = index + 1 + k = k // 10 + + self.groups[i] = result - self.cu = CUNumber._processNumberPlain(self.arabic) return self - def processNumberDelim(self): + def breakIntoGroups(self): + "Break the Arabic number into groups of 3 digits." + + while self.arabic > 0: + self.groups.append(self.arabic % 1000) + self.arabic = self.arabic // 1000 - self.cu = CUNumber._processNumberDelim(self.arabic) return self def convert(self): "Convert the Arabic number to Cyrillic." - if self.arabic < 1001 or self.hasFlag(CU_PLAIN): - self.processNumberPlain() - else: - self.processNumberDelim() - return self.stripDelimDots().prependDot().appendDot().appendTitlo() + return ( + self.breakIntoGroups() + .translateGroups() + .appendThousandMarks() + .purgeEmptyGroups() + .swapDigits() + .delimDots() + .build() + .appendTitlo() + .wrapDot() + .get() + ) class ArabicNumber: @@ -282,13 +318,13 @@ def isinstance(input, condition, msg): def to_cu(input, flags=0): """ - Convert a number into Cyrillic numeral system. + Convert a number into Cyrillic numeral system. Uses plain style by default. Requires a non-zero integer. """ if isinstance(input, int, "Non-zero integer required, got {0}"): - return CUNumber(input, flags).convert().get() + return CUNumber(input, flags).convert() def to_arab(input, flags=0): From 91da4f85b94e01a5a2059477e504f34bee3cf19a Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 22 Dec 2021 20:28:24 +0300 Subject: [PATCH 02/39] Plain style as default --- cunumbers/__init__.py | 2 +- cunumbers/cunumbers.py | 15 ++++++++------- tests/test_cunumbers.py | 28 ++++++++++++++-------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/cunumbers/__init__.py b/cunumbers/__init__.py index 4aaf871..c432136 100644 --- a/cunumbers/__init__.py +++ b/cunumbers/__init__.py @@ -1,7 +1,7 @@ __all__ = [ "to_cu", "to_arab", - "CU_PLAIN", + "CU_DELIM", "CU_NOTITLO", "CU_ENDDOT", "CU_DELIMDOT", diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index a53e29a..c147261 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -5,12 +5,13 @@ import re -CU_DELIM = 0x1 # Write in delim style -CU_PLAIN = 0x10 # Read/write in plain style +CU_PLAIN = 0x1 # Write in plain style +CU_DELIM = 0x10 # Read/write in delim style CU_NOTITLO = 0x100 # DO NOT append titlo CU_ENDDOT = 0x1000 # Append dot CU_PREDOT = 0x10000 # Prepend dot -CU_DELIMDOT = 0x100000 | CU_DELIM # Delimeter dots (delim mode only) +CU_DOT = 0x100000 +CU_DELIMDOT = CU_DOT | CU_DELIM # Delimeter dots (forces delim style) CU_WRAPDOT = CU_ENDDOT | CU_PREDOT # Wrap in dots CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots @@ -89,7 +90,7 @@ def wrapDot(self): def delimDots(self): "Insert dots between digit groups if appropriate flag is set." - if self.hasFlag(CU_DELIMDOT): + if self.hasFlag(CU_DOT): for i, k in enumerate(self.groups[1:]): self.groups[i + 1] = k + cu_dot @@ -130,9 +131,9 @@ def appendThousandMarks(self): "Append thousand marks according to chosen style (plain or delimeter)." method = ( - CUNumber.appendThousandMarksPlain - if self.hasFlag(CU_PLAIN) - else CUNumber.appendThousandMarksDelim + CUNumber.appendThousandMarksDelim + if self.hasFlag(CU_DELIM) + else CUNumber.appendThousandMarksPlain ) for i, k in enumerate(self.groups): diff --git a/tests/test_cunumbers.py b/tests/test_cunumbers.py index 1acd952..c97e7c4 100644 --- a/tests/test_cunumbers.py +++ b/tests/test_cunumbers.py @@ -20,35 +20,35 @@ def testToCUHundreds(self): def testToCUThousand(self): self.assertEqual(to_cu(1000), "҂а҃") - self.assertEqual(to_cu(1006, CU_PLAIN), "҂а҃ѕ") - self.assertEqual(to_cu(1010, CU_PLAIN), "҂а҃і") + 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, CU_PLAIN), "҂і҂а҃") + self.assertEqual(to_cu(11000), "҂і҂а҃") def testToCUBig(self): - self.assertEqual(to_cu(10001010001, CU_PLAIN), "҂҂҂і҂҂а҂і҃а") - self.assertEqual(to_cu(50000000000, CU_PLAIN), "҂҂҂н҃") - self.assertEqual(to_cu(60000070000, CU_PLAIN), "҂҂҂ѯ҂ѻ҃") - self.assertEqual(to_cu(111111111, CU_PLAIN), "҂҂р҂҂і҂҂а҂р҂і҂ара҃і") + 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 testToCUDelimThousand(self): - self.assertEqual(to_cu(1010), "҂а.і҃") - self.assertEqual(to_cu(11000), "҂а҃і") + self.assertEqual(to_cu(1010, CU_DELIM), "҂а.і҃") + self.assertEqual(to_cu(11000, CU_DELIM), "҂а҃і") def testToCUDelimBig(self): - self.assertEqual(to_cu(10001010001), "҂҂҂і҂҂а҂і҃а") - self.assertEqual(to_cu(50000000000), "҂҂҂н҃") - self.assertEqual(to_cu(60000070000), "҂҂҂ѯ҂ѻ҃") - self.assertEqual(to_cu(111111111), "҂҂раі҂раіра҃і") + 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_PLAIN + CU_NOTITLO), "҂і҂а") + self.assertEqual(to_cu(11000, CU_NOTITLO), "҂і҂а") def testToCUEnddot(self): self.assertEqual(to_cu(1, CU_ENDDOT), "а҃.") From 82a61a30d5c60c5011d9fb8e92f00c622972b2ad Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Thu, 11 Aug 2022 00:11:42 +0300 Subject: [PATCH 03/39] Ambiguity check for DELIM style numbers --- cunumbers/cunumbers.py | 15 ++++++++++++++- tests/test_cunumbers.py | 8 +++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index c147261..793b1ce 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -10,7 +10,7 @@ CU_NOTITLO = 0x100 # DO NOT append titlo CU_ENDDOT = 0x1000 # Append dot CU_PREDOT = 0x10000 # Prepend dot -CU_DOT = 0x100000 +CU_DOT = 0x100000 # Delimeter dots (private, for internal flag checks) CU_DELIMDOT = CU_DOT | CU_DELIM # Delimeter dots (forces delim style) CU_WRAPDOT = CU_ENDDOT | CU_PREDOT # Wrap in dots CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots @@ -190,6 +190,18 @@ def translateGroups(self): return self + def ambiguityCheck(self): + if self.hasFlag(CU_DELIM): + try: + if (self.groups[0] // 10 % 10 == 1) and ( + self.groups[1] // 10 % 10 == 0 + ): + self.flags = self.flags | CU_DOT + finally: + return self + else: + return self + def breakIntoGroups(self): "Break the Arabic number into groups of 3 digits." @@ -204,6 +216,7 @@ def convert(self): return ( self.breakIntoGroups() + .ambiguityCheck() .translateGroups() .appendThousandMarks() .purgeEmptyGroups() diff --git a/tests/test_cunumbers.py b/tests/test_cunumbers.py index c97e7c4..6aadbe7 100644 --- a/tests/test_cunumbers.py +++ b/tests/test_cunumbers.py @@ -34,9 +34,15 @@ def testToCUBig(self): class ToCUDelimTestCase(unittest.TestCase): - def testToCUDelimThousand(self): + 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), "҂҂҂і҂҂а҂і҃а") From 81883e8394c80c55f6e57fe8f5ed2df911c11cc7 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Thu, 11 Aug 2022 00:10:21 +0300 Subject: [PATCH 04/39] Centralised flag checking --- cunumbers/cunumbers.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index 793b1ce..e4d3294 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -76,30 +76,26 @@ def build(self): self.cu = k + self.cu return self - def wrapDot(self): + def wrapDot(self, cond_a, cond_b): "Prepend and/or append dots if appropriate flags are set." - self.cu = ( - (cu_dot if self.hasFlag(CU_PREDOT) else "") - + self.cu - + (cu_dot if self.hasFlag(CU_ENDDOT) else "") - ) + self.cu = (cu_dot if cond_a else "") + self.cu + (cu_dot if cond_b else "") return self - def delimDots(self): + def delimDots(self, cond): "Insert dots between digit groups if appropriate flag is set." - if self.hasFlag(CU_DOT): + if cond: for i, k in enumerate(self.groups[1:]): self.groups[i + 1] = k + cu_dot return self - def appendTitlo(self): + def appendTitlo(self, cond): "Append titlo unless appropriate flag is set." - if not self.hasFlag(CU_NOTITLO): + if not cond: result = re.subn( "([\S]+)(?{0}\g<2>".format(cu_titlo), @@ -127,12 +123,12 @@ def appendThousandMarksPlain(input, index): return result - def appendThousandMarks(self): + def appendThousandMarks(self, cond): "Append thousand marks according to chosen style (plain or delimeter)." method = ( CUNumber.appendThousandMarksDelim - if self.hasFlag(CU_DELIM) + if cond else CUNumber.appendThousandMarksPlain ) @@ -190,13 +186,13 @@ def translateGroups(self): return self - def ambiguityCheck(self): - if self.hasFlag(CU_DELIM): + def ambiguityCheck(self, cond, flag): + if cond: try: if (self.groups[0] // 10 % 10 == 1) and ( self.groups[1] // 10 % 10 == 0 ): - self.flags = self.flags | CU_DOT + self.flags = self.flags | flag finally: return self else: @@ -216,15 +212,15 @@ def convert(self): return ( self.breakIntoGroups() - .ambiguityCheck() + .ambiguityCheck(self.hasFlag(CU_DELIM), CU_DOT) .translateGroups() - .appendThousandMarks() + .appendThousandMarks(self.hasFlag(CU_DELIM)) .purgeEmptyGroups() .swapDigits() - .delimDots() + .delimDots(self.hasFlag(CU_DOT)) .build() - .appendTitlo() - .wrapDot() + .appendTitlo(self.hasFlag(CU_NOTITLO)) + .wrapDot(self.hasFlag(CU_PREDOT), self.hasFlag(CU_ENDDOT)) .get() ) From d8c5df77e6b1de5d9d11714a30d81c6657daec7d Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 10 Aug 2022 23:35:48 +0300 Subject: [PATCH 05/39] Refactor Arabic conversion --- cunumbers/cunumbers.py | 116 ++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 66 deletions(-) diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index e4d3294..ab1a70a 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -27,21 +27,8 @@ 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, - ) +cu_regex = "{0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?)".format( + cu_thousand, cu_hundreds, cu_tens[1:], cu_digits, cu_tens[0] ) @@ -229,9 +216,12 @@ class ArabicNumber: def __init__(self, input): self.cu = input self.arabic = 0 + self.groups = [] self.prepare() def get(self): + "Return the Arabic number integer representation." + return self.arabic def prepare(self): @@ -246,77 +236,71 @@ def prepare(self): else: raise ValueError("Non-empty string required") - def processDigit(input, registry=0): - "Convert the Cyrillic numeral to an arabic digit." + def getDigit(input): + "Get Arabic digit for the given CU digit." index = cu_dict.index(input) # Find current digit in dictionary - number = index % 10 # Digit - registry = index // 10 # Digit registry - return number * pow(10, registry) # Resulting number - - def processGroup(input, group=0): - "Process the group of Cyrillic numerals." - - subtotal = multiplier = 0 - for k in input: - if k == cu_thousand: - multiplier += 1 - continue - subtotal += ArabicNumber.processDigit(k) + number = index % 10 # Get the digit + registry = index // 10 # Get digit registry - # Multiply result by 1000 - times "҂" marks or current group - return subtotal * pow(1000, max(multiplier, group)) + return number * pow(10, registry) - def prepareGroups(input, regex): - "Prepare Cyrillic numeral groups for conversion." + def calculateMultiplier(index, input): + "Calculate multiplier for adjusting digit group value to its registry." - groups = re.split(regex, input) + multiplier = ( + re.match("({0}*)".format(cu_thousand), input).groups()[0].count(cu_thousand) + ) # Count trailing thousand marks in the group + multiplier = pow(1000, multiplier if multiplier else index - 1) + # Use thousand marks if present, otherwise use group index + return multiplier - while groups.count(""): # Purge empty strs from collection - groups.remove("") - groups.reverse() - return groups + def translateGroups(self): + "Translate the Cyrillic number per group." - def _processNumberPlain(input): - "Process the Cyrillic number per digit." + for i, k in enumerate(self.groups): - groups = ArabicNumber.prepareGroups(input, cu_plain_regex) + subtotal = 0 # Current group total value - result = 0 - for k in groups: - result += ArabicNumber.processGroup(k) - return result + multiplier = ArabicNumber.calculateMultiplier(i, k) + k = re.sub(cu_thousand, "", k) # Strip thousand marks + for l in k: + subtotal += ArabicNumber.getDigit(l) - def _processNumberDelim(input): - "Process the Cyrillic number per groups of 1-3 digits." + self.arabic += subtotal * multiplier - groups = ArabicNumber.prepareGroups(input, cu_delim_regex) + return self - result = 0 - for i, k in enumerate(groups): - result += ArabicNumber.processGroup(k, i) - return result + def breakIntoGroups(self, regex): + "Break the Cyrillic number in groups of 1-3 digits." - def processNumberPlain(self): - self.arabic = ArabicNumber._processNumberPlain(self.cu) - return self + self.groups = re.split(regex, self.cu) # Break into groups + for i, k in enumerate(self.groups): + self.groups.pop(i) if not k else True # Purge empty groups + self.groups.reverse() # Reverse groups (to ascending order) - def processNumberDelim(self): - self.arabic = ArabicNumber._processNumberDelim(self.cu) return self - def convert(self): - "Choose the Cyrillic number to Arabic." + def validate(self, regex): + "Validate that input is a Cyrillic number." - 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() + if re.fullmatch(regex, self.cu): + return self else: raise ValueError( "String does not match any pattern for Cyrillic numeral system number" ) + def convert(self): + "Convert the Cyrillic number to Arabic." + + return ( + self.validate("({0})+".format(cu_regex)) + .breakIntoGroups("({0})".format(cu_regex)) + .translateGroups() + .get() + ) + def isinstance(input, condition, msg): t = type(input) @@ -339,10 +323,10 @@ def to_cu(input, flags=0): def to_arab(input, flags=0): """ - Convert a number into Arabic numeral system . + Convert a number into Arabic numeral system. Requires a non-empty string. """ if isinstance(input, str, "Non-empty string required, got {0}"): - return ArabicNumber(input).convert().get() + return ArabicNumber(input).convert() From 3d3e093744d3bc3f066e601f51d1fbb89e278902 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Tue, 16 Aug 2022 18:35:58 +0300 Subject: [PATCH 06/39] Refactor dictionary as Enum --- cunumbers/cunumbers.py | 171 +++++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 59 deletions(-) diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index ab1a70a..9a1f48a 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -4,6 +4,7 @@ "Module for number conversion between Arabic and Cyrillic numeral systems." import re +from enum import Enum, unique CU_PLAIN = 0x1 # Write in plain style CU_DELIM = 0x10 # Read/write in delim style @@ -15,20 +16,76 @@ 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" # 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 -) +@unique +class Numerals(Enum): + @classmethod + def get(cls, input): + try: + return cls[input].value + except: + try: + return cls(input).name + except: + return "" + + @classmethod + def __getmany(cls, start=1, end=10, step=1): + r = "" + for i in range(start * step, end * step, step): + r += cls(i).name + return r + + @classmethod + def digits(cls, start=1, end=10): + return cls.__getmany(start, end, 1) + + @classmethod + def tens(cls, start=1, end=10): + return cls.__getmany(start, end, 10) + + @classmethod + def hundreds(cls, start=1, end=10): + return cls.__getmany(start, end, 100) + + а = 1 + в = 2 + г = 3 + д = 4 + є = 5 + ѕ = 6 + з = 7 + и = 8 + ѳ = 9 + і = 10 + к = 20 + л = 30 + м = 40 + н = 50 + ѯ = 60 + ѻ = 70 + п = 80 + ч = 90 + р = 100 + с = 200 + т = 300 + у = 400 + ф = 500 + х = 600 + ѱ = 700 + ѿ = 800 + ц = 900 + THOUSAND = "҂" # "Thousand" mark + TITLO = "҃" # "Titlo" decorator + DOT = "." # Dot decorator + cu_regex = "{0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?)".format( - cu_thousand, cu_hundreds, cu_tens[1:], cu_digits, cu_tens[0] + Numerals.get("THOUSAND"), + Numerals.hundreds(), + Numerals.tens(2), + Numerals.digits(), + Numerals.get(10), ) @@ -66,7 +123,11 @@ def build(self): def wrapDot(self, cond_a, cond_b): "Prepend and/or append dots if appropriate flags are set." - self.cu = (cu_dot if cond_a else "") + self.cu + (cu_dot if cond_b else "") + self.cu = ( + (Numerals.get("DOT") if cond_a else "") + + self.cu + + (Numerals.get("DOT") if cond_b else "") + ) return self @@ -75,7 +136,7 @@ def delimDots(self, cond): if cond: for i, k in enumerate(self.groups[1:]): - self.groups[i + 1] = k + cu_dot + self.groups[i + 1] = k + Numerals.get("DOT") return self @@ -84,11 +145,36 @@ def appendTitlo(self, cond): if not cond: result = re.subn( - "([\S]+)(?{0}\g<2>".format(cu_titlo), + "([\S]+)(?{0}\g<2>".format(Numerals.get("TITLO")), self.cu, ) - self.cu = result[0] if result[1] > 0 else self.cu + cu_titlo + self.cu = result[0] if result[1] > 0 else self.cu + Numerals.get("TITLO") + + return self + + def swapDigits(self): + "Swap digits in 11-19." + + for i, k in enumerate(self.groups): + + self.groups[i] = re.sub( + "({0})([{1}])".format(Numerals.get(10), Numerals.digits()), + "\g<2>\g<1>", + self.groups[i], + ) + + return self + + def purgeEmptyGroups(self): + "Remove empty groups from digit group collection." + + for i, k in enumerate(self.groups): + + if not k: + self.groups.pop(i) return self @@ -96,7 +182,7 @@ def appendThousandMarksDelim(input, index): "Append thousand marks in delimeter style." if input: - return cu_thousand * index + input + return Numerals.get("THOUSAND") * index + input else: return "" @@ -125,36 +211,10 @@ def appendThousandMarks(self, cond): return self - def swapDigits(self): - "Swap digits in 11-19." - - for i, k in enumerate(self.groups): - - self.groups[i] = re.sub( - "({0})([{1}])".format(cu_tens[0], cu_digits), - "\g<2>\g<1>", - self.groups[i], - ) - - return self - - def purgeEmptyGroups(self): - "Remove empty groups from digit group collection." - - for i, k in enumerate(self.groups): - - if not k: - self.groups.pop(i) - - return self - - def getDigit(input, index): + def getDigit(input): "Get CU digit for given Arabic digit." - if input: - return cu_dict[input + 10 * index] - else: - return "" + return Numerals.get(input) if input else "" def translateGroups(self): "Translate the Arabic number per group." @@ -165,7 +225,7 @@ def translateGroups(self): index = 0 while k > 0: - result = CUNumber.getDigit(k % 10, index) + result + result = CUNumber.getDigit(k % 10 * pow(10, index)) + result index = index + 1 k = k // 10 @@ -229,27 +289,20 @@ def prepare(self): if self.cu: self.cu = re.sub( - "[{0}\.]".format(cu_titlo), "", self.cu + "[{0}\.]".format(Numerals.get("TITLO")), "", self.cu ) # Strip ҃"҃ " and dots self.cu = str.strip(self.cu) self.cu = str.lower(self.cu) else: raise ValueError("Non-empty string required") - def getDigit(input): - "Get Arabic digit for the given CU digit." - - index = cu_dict.index(input) # Find current digit in dictionary - number = index % 10 # Get the digit - registry = index // 10 # Get digit registry - - return number * pow(10, registry) - def calculateMultiplier(index, input): "Calculate multiplier for adjusting digit group value to its registry." multiplier = ( - re.match("({0}*)".format(cu_thousand), input).groups()[0].count(cu_thousand) + re.match("({0}*)".format(Numerals.get("THOUSAND")), input) + .groups()[0] + .count(Numerals.get("THOUSAND")) ) # Count trailing thousand marks in the group multiplier = pow(1000, multiplier if multiplier else index - 1) # Use thousand marks if present, otherwise use group index @@ -263,9 +316,9 @@ def translateGroups(self): subtotal = 0 # Current group total value multiplier = ArabicNumber.calculateMultiplier(i, k) - k = re.sub(cu_thousand, "", k) # Strip thousand marks + k = re.sub(Numerals.get("THOUSAND"), "", k) # Strip thousand marks for l in k: - subtotal += ArabicNumber.getDigit(l) + subtotal += Numerals.get(l) self.arabic += subtotal * multiplier From dc951b6825704257104ba07d408f44089fb9b329 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 01:18:36 +0300 Subject: [PATCH 07/39] Detach Dictionary module --- cunumbers/cunumbers.py | 33 ++------------------------------- cunumbers/dictionary.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 cunumbers/dictionary.py diff --git a/cunumbers/cunumbers.py b/cunumbers/cunumbers.py index 9a1f48a..1bee1d9 100644 --- a/cunumbers/cunumbers.py +++ b/cunumbers/cunumbers.py @@ -4,7 +4,7 @@ "Module for number conversion between Arabic and Cyrillic numeral systems." import re -from enum import Enum, unique +from cunumbers.dictionary import HebrewTypeDictionary CU_PLAIN = 0x1 # Write in plain style CU_DELIM = 0x10 # Read/write in delim style @@ -17,36 +17,7 @@ CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots -@unique -class Numerals(Enum): - @classmethod - def get(cls, input): - try: - return cls[input].value - except: - try: - return cls(input).name - except: - return "" - - @classmethod - def __getmany(cls, start=1, end=10, step=1): - r = "" - for i in range(start * step, end * step, step): - r += cls(i).name - return r - - @classmethod - def digits(cls, start=1, end=10): - return cls.__getmany(start, end, 1) - - @classmethod - def tens(cls, start=1, end=10): - return cls.__getmany(start, end, 10) - - @classmethod - def hundreds(cls, start=1, end=10): - return cls.__getmany(start, end, 100) +class Numerals(HebrewTypeDictionary): а = 1 в = 2 diff --git a/cunumbers/dictionary.py b/cunumbers/dictionary.py new file mode 100644 index 0000000..bb3f8ae --- /dev/null +++ b/cunumbers/dictionary.py @@ -0,0 +1,37 @@ +# -*- coding: UTF-8 -*- +# For licensing information see LICENSE file included in the project's root directory. + +from enum import Enum, unique + + +class Dictionary(Enum): + @classmethod + def get(cls, input): + try: + return cls[input].value + except: + try: + return cls(input).name + except: + return "" + + +class HebrewTypeDictionary(Dictionary): + @classmethod + def __getmany(cls, start=1, end=10, step=1): + r = "" + for i in range(start * step, end * step, step): + r += cls(i).name + return r + + @classmethod + def digits(cls, start=1, end=10): + return cls.__getmany(start, end, 1) + + @classmethod + def tens(cls, start=1, end=10): + return cls.__getmany(start, end, 10) + + @classmethod + def hundreds(cls, start=1, end=10): + return cls.__getmany(start, end, 100) From f0651dd03b22d74ee793fc7852c8e1a1dd915fd7 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Thu, 18 Aug 2022 00:39:08 +0300 Subject: [PATCH 08/39] Omninumeric initial commit Detached core conversion tools Moved core dictionary and conversion tools into omninumeric.py module Detached all CU-specific logic to cyrillic .py module --- cunumbers/__init__.py | 10 - cunumbers/dictionary.py | 37 ---- docs/README.md | 2 +- docs/README.ru.md | 2 +- omninumeric/__init__.py | 7 + omninumeric/cyrillic/__init__.py | 12 ++ .../cyrillic/cyrillic.py | 192 +++++++----------- omninumeric/omninumeric.py | 110 ++++++++++ setup.cfg | 14 +- tests/{test_cunumbers.py => test_cyrillic.py} | 2 +- 10 files changed, 219 insertions(+), 169 deletions(-) delete mode 100644 cunumbers/__init__.py delete mode 100644 cunumbers/dictionary.py create mode 100644 omninumeric/__init__.py create mode 100644 omninumeric/cyrillic/__init__.py rename cunumbers/cunumbers.py => omninumeric/cyrillic/cyrillic.py (61%) create mode 100644 omninumeric/omninumeric.py rename tests/{test_cunumbers.py => test_cyrillic.py} (99%) diff --git a/cunumbers/__init__.py b/cunumbers/__init__.py deleted file mode 100644 index c432136..0000000 --- a/cunumbers/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -__all__ = [ - "to_cu", - "to_arab", - "CU_DELIM", - "CU_NOTITLO", - "CU_ENDDOT", - "CU_DELIMDOT", - "CU_WRAPDOT", - "CU_ALLDOT", -] diff --git a/cunumbers/dictionary.py b/cunumbers/dictionary.py deleted file mode 100644 index bb3f8ae..0000000 --- a/cunumbers/dictionary.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: UTF-8 -*- -# For licensing information see LICENSE file included in the project's root directory. - -from enum import Enum, unique - - -class Dictionary(Enum): - @classmethod - def get(cls, input): - try: - return cls[input].value - except: - try: - return cls(input).name - except: - return "" - - -class HebrewTypeDictionary(Dictionary): - @classmethod - def __getmany(cls, start=1, end=10, step=1): - r = "" - for i in range(start * step, end * step, step): - r += cls(i).name - return r - - @classmethod - def digits(cls, start=1, end=10): - return cls.__getmany(start, end, 1) - - @classmethod - def tens(cls, start=1, end=10): - return cls.__getmany(start, end, 10) - - @classmethod - def hundreds(cls, start=1, end=10): - return cls.__getmany(start, end, 100) diff --git a/docs/README.md b/docs/README.md index e246f5e..99c1bfc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# cu-numbers +# Omninumeric ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cu-numbers) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/cu-numbers) [![Codecov](https://img.shields.io/codecov/c/github/endrain/cu-numbers)](https://app.codecov.io/gh/endrain/cu-numbers) diff --git a/docs/README.ru.md b/docs/README.ru.md index 38ab3e4..eb8bbc0 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -1,4 +1,4 @@ -# cu-numbers +# Omninumeric ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cu-numbers) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/cu-numbers) [![Codecov](https://img.shields.io/codecov/c/github/endrain/cu-numbers)](https://app.codecov.io/gh/endrain/cu-numbers) diff --git a/omninumeric/__init__.py b/omninumeric/__init__.py new file mode 100644 index 0000000..477733c --- /dev/null +++ b/omninumeric/__init__.py @@ -0,0 +1,7 @@ +from .omninumeric import ( + Dictionary, + GreekTypeDictionary, + ArabicNumberConverter, + AlphabeticNumberConverter, + isinstance, +) diff --git a/omninumeric/cyrillic/__init__.py b/omninumeric/cyrillic/__init__.py new file mode 100644 index 0000000..17cfa1e --- /dev/null +++ b/omninumeric/cyrillic/__init__.py @@ -0,0 +1,12 @@ +from .cyrillic import ( + to_alphabetic, + to_arabic, + to_cu, + to_arab, + CU_DELIM, + CU_NOTITLO, + CU_ENDDOT, + CU_DELIMDOT, + CU_WRAPDOT, + CU_ALLDOT, +) diff --git a/cunumbers/cunumbers.py b/omninumeric/cyrillic/cyrillic.py similarity index 61% rename from cunumbers/cunumbers.py rename to omninumeric/cyrillic/cyrillic.py index 1bee1d9..01104c9 100644 --- a/cunumbers/cunumbers.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -4,7 +4,7 @@ "Module for number conversion between Arabic and Cyrillic numeral systems." import re -from cunumbers.dictionary import HebrewTypeDictionary +from omninumeric import * CU_PLAIN = 0x1 # Write in plain style CU_DELIM = 0x10 # Read/write in delim style @@ -17,7 +17,7 @@ CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots -class Numerals(HebrewTypeDictionary): +class CyrillicDictionary(GreekTypeDictionary): а = 1 в = 2 @@ -51,53 +51,26 @@ class Numerals(HebrewTypeDictionary): DOT = "." # Dot decorator -cu_regex = "{0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?)".format( - Numerals.get("THOUSAND"), - Numerals.hundreds(), - Numerals.tens(2), - Numerals.digits(), - Numerals.get(10), -) +class ArabicNumber(ArabicNumberConverter): + dict = CyrillicDictionary - -class CUNumber: - def __init__(self, input, flags=0): - self.cu = "" - self.arabic = input - self.flags = flags - self.groups = [] - self.prepare() - - def get(self): - "Return the CU number string representation." - - return self.cu - - def prepare(self): - "Prepare the Arabic number for conversion." - - if self.arabic <= 0: - raise ValueError("Non-zero integer required") - - def hasFlag(self, flag): - "Check if a flag is set." - - return False if self.flags & flag == 0 else True + def __init__(self, value, flags=0): + super().__init__(value, flags) def build(self): "Build the CU number from digit groups." for k in self.groups: - self.cu = k + self.cu + self.alphabetic = k + self.alphabetic return self def wrapDot(self, cond_a, cond_b): "Prepend and/or append dots if appropriate flags are set." - self.cu = ( - (Numerals.get("DOT") if cond_a else "") - + self.cu - + (Numerals.get("DOT") if cond_b else "") + self.alphabetic = ( + (self.dict.get("DOT") if cond_a else "") + + self.alphabetic + + (self.dict.get("DOT") if cond_b else "") ) return self @@ -107,7 +80,7 @@ def delimDots(self, cond): if cond: for i, k in enumerate(self.groups[1:]): - self.groups[i + 1] = k + Numerals.get("DOT") + self.groups[i + 1] = k + self.dict.get("DOT") return self @@ -117,12 +90,14 @@ def appendTitlo(self, cond): if not cond: result = re.subn( "([\S]+)(?{0}\g<2>".format(Numerals.get("TITLO")), - self.cu, + "\g<1>{0}\g<2>".format(self.dict.get("TITLO")), + self.alphabetic, + ) + self.alphabetic = ( + result[0] if result[1] > 0 else self.alphabetic + self.dict.get("TITLO") ) - self.cu = result[0] if result[1] > 0 else self.cu + Numerals.get("TITLO") return self @@ -132,7 +107,7 @@ def swapDigits(self): for i, k in enumerate(self.groups): self.groups[i] = re.sub( - "({0})([{1}])".format(Numerals.get(10), Numerals.digits()), + "({0})([{1}])".format(self.dict.get(10), self.dict.digits()), "\g<2>\g<1>", self.groups[i], ) @@ -149,21 +124,23 @@ def purgeEmptyGroups(self): return self - def appendThousandMarksDelim(input, index): + @classmethod + def appendThousandMarksDelim(cls, input, index): "Append thousand marks in delimeter style." if input: - return Numerals.get("THOUSAND") * index + input + return cls.dict.get("THOUSAND") * index + input else: return "" - def appendThousandMarksPlain(input, index): + @classmethod + def appendThousandMarksPlain(cls, input, index): "Append thousand marks in plain style." result = "" for i in input: - result = result + CUNumber.appendThousandMarksDelim(i, index) + result = result + cls.appendThousandMarksDelim(i, index) return result @@ -171,9 +148,7 @@ def appendThousandMarks(self, cond): "Append thousand marks according to chosen style (plain or delimeter)." method = ( - CUNumber.appendThousandMarksDelim - if cond - else CUNumber.appendThousandMarksPlain + self.appendThousandMarksDelim if cond else self.appendThousandMarksPlain ) for i, k in enumerate(self.groups): @@ -182,10 +157,11 @@ def appendThousandMarks(self, cond): return self - def getDigit(input): + @classmethod + def getDigit(cls, input): "Get CU digit for given Arabic digit." - return Numerals.get(input) if input else "" + return cls.dict.get(input) if input else "" def translateGroups(self): "Translate the Arabic number per group." @@ -196,7 +172,7 @@ def translateGroups(self): index = 0 while k > 0: - result = CUNumber.getDigit(k % 10 * pow(10, index)) + result + result = self.getDigit(k % 10 * pow(10, index)) + result index = index + 1 k = k // 10 @@ -243,114 +219,104 @@ def convert(self): ) -class ArabicNumber: - def __init__(self, input): - self.cu = input - self.arabic = 0 - self.groups = [] - self.prepare() +class CyrillicNumber(AlphabeticNumberConverter): - def get(self): - "Return the Arabic number integer representation." + dict = CyrillicDictionary - return self.arabic + regex = "{0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?)".format( + dict.get("THOUSAND"), + dict.hundreds(), + dict.tens(2), + dict.digits(), + dict.get(10), + ) + + def validate(self, regex): + "Validate that input is a Cyrillic number." + + if re.fullmatch(regex, self.alphabetic): + return self + else: + raise ValueError( + "String does not match any pattern for Cyrillic numeral system number" + ) + + def __init__(self, alphabetic): + super().__init__(alphabetic) + self.validate("({0})+".format(self.regex)) def prepare(self): "Prepare the Cyrillic number for conversion." - if self.cu: - self.cu = re.sub( - "[{0}\.]".format(Numerals.get("TITLO")), "", self.cu + if super().prepare(): + self.alphabetic = re.sub( + "[{0}\.]".format(self.dict.get("TITLO")), "", self.alphabetic ) # Strip ҃"҃ " and dots - self.cu = str.strip(self.cu) - self.cu = str.lower(self.cu) - else: - raise ValueError("Non-empty string required") + return self - def calculateMultiplier(index, input): + @classmethod + def calculateMultiplier(cls, index, input): "Calculate multiplier for adjusting digit group value to its registry." multiplier = ( - re.match("({0}*)".format(Numerals.get("THOUSAND")), input) + re.match("({0}*)".format(cls.dict.get("THOUSAND")), input) .groups()[0] - .count(Numerals.get("THOUSAND")) + .count(cls.dict.get("THOUSAND")) ) # Count trailing thousand marks in the group multiplier = pow(1000, multiplier if multiplier else index - 1) # Use thousand marks if present, otherwise use group index return multiplier def translateGroups(self): - "Translate the Cyrillic number per group." + "Translate the alphabetic number per group." for i, k in enumerate(self.groups): - subtotal = 0 # Current group total value - - multiplier = ArabicNumber.calculateMultiplier(i, k) - k = re.sub(Numerals.get("THOUSAND"), "", k) # Strip thousand marks - for l in k: - subtotal += Numerals.get(l) - - self.arabic += subtotal * multiplier + multiplier = self.calculateMultiplier(i, k) + k = re.sub(self.dict.get("THOUSAND"), "", k) # Strip thousand marks + self.arabic += self.translate(k) * multiplier return self def breakIntoGroups(self, regex): "Break the Cyrillic number in groups of 1-3 digits." - self.groups = re.split(regex, self.cu) # Break into groups + self.groups = re.split(regex, self.alphabetic) # Break into groups for i, k in enumerate(self.groups): self.groups.pop(i) if not k else True # Purge empty groups self.groups.reverse() # Reverse groups (to ascending order) return self - def validate(self, regex): - "Validate that input is a Cyrillic number." - - if re.fullmatch(regex, self.cu): - return self - else: - raise ValueError( - "String does not match any pattern for Cyrillic numeral system number" - ) - def convert(self): "Convert the Cyrillic number to Arabic." - return ( - self.validate("({0})+".format(cu_regex)) - .breakIntoGroups("({0})".format(cu_regex)) - .translateGroups() - .get() - ) - + return self.breakIntoGroups("({0})".format(self.regex)).translateGroups().get() -def isinstance(input, condition, msg): - t = type(input) - if t == condition: - return True - else: - raise TypeError(msg.format(t)) - -def to_cu(input, flags=0): +def to_alphabetic(input, flags=0): """ - Convert a number into Cyrillic numeral system. Uses plain style by default. + Convert an Arabic number into Cyrillic numeral system. Uses plain style by default. Requires a non-zero integer. """ if isinstance(input, int, "Non-zero integer required, got {0}"): - return CUNumber(input, flags).convert() + return ArabicNumber(input, flags).convert() + + +to_cu = to_alphabetic -def to_arab(input, flags=0): +def to_arabic(input, flags=0): """ - Convert a number into Arabic numeral system. + Convert a Cyrillic number into Arabic numeral system. Requires a non-empty string. """ if isinstance(input, str, "Non-empty string required, got {0}"): - return ArabicNumber(input).convert() + return CyrillicNumber(input).convert() + + +to_arab = to_arabic diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py new file mode 100644 index 0000000..a8c9667 --- /dev/null +++ b/omninumeric/omninumeric.py @@ -0,0 +1,110 @@ +# -*- coding: UTF-8 -*- +# For licensing information see LICENSE file included in the project's root directory. + +from enum import Enum, unique + + +class Dictionary(Enum): + @classmethod + def get(cls, input): + try: + return cls[input].value + except: + try: + return cls(input).name + except: + return "" + + +class GreekTypeDictionary(Dictionary): + @classmethod + def __getmany(cls, start=1, end=10, step=1): + r = "" + for i in range(start * step, end * step, step): + r += cls(i).name + return r + + @classmethod + def digits(cls, start=1, end=10): + return cls.__getmany(start, end, 1) + + @classmethod + def tens(cls, start=1, end=10): + return cls.__getmany(start, end, 10) + + @classmethod + def hundreds(cls, start=1, end=10): + return cls.__getmany(start, end, 100) + + +class ArabicNumberConverter: + + dict = Dictionary + + def prepare(self): + "Prepare the Arabic number for conversion." + + if self.arabic <= 0: + raise ValueError("Non-zero integer required") + + def __init__(self, value, flags=0): + self.alphabetic = "" + self.arabic = value + self.flags = flags + self.groups = [] + self.prepare() + + def get(self): + "Return the alphabetic number representation." + + return self.alphabetic + + def hasFlag(self, flag): + "Check if a flag is set." + + return self.flags & flag + # return False if self.flags & flag == 0 else True + + +class AlphabeticNumberConverter: + + dict = Dictionary + + def prepare(self): + "Prepare the alphabetic number for conversion." + + if self.alphabetic: + self.alphabetic = str.lower(str.strip(self.alphabetic)) + # self.alphabetic = str.lower(self.alphabetic) + return self + else: + raise ValueError("Non-empty string required") + + def __init__(self, alphabetic): + + self.alphabetic = alphabetic + self.arabic = 0 + self.groups = [] + self.prepare() + + def get(self): + "Return the Arabic number representation." + + return self.arabic + + @classmethod + def translate(cls, alphabetic): + + total = 0 # Current group total value + for k in alphabetic: + total += cls.dict.get(k) + + return total + + +def isinstance(input, condition, msg): + t = type(input) + if t == condition: + return True + else: + raise TypeError(msg.format(t)) diff --git a/setup.cfg b/setup.cfg index e58a4b8..6a443a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,16 @@ [metadata] -name = cu-numbers -version = 1.3.2 +name = omninumeric +version = 1.0.0 author = Andrei Shur author_email = amshoor@gmail.com -description = Cyrillic numeral system numbers conversion +description = Convert numbers between Arabic and alphabetic numeral systems long_description = file: README.md long_description_content_type = text/markdown -url = https://github.com/endrain/cu-numbers +url = https://github.com/endrain/omninumeric keywords = church slavonic, conversion license = MIT classifiers = - Development Status :: 5 - Production/Stable + Development Status :: 4 - Beta Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 @@ -25,5 +25,7 @@ classifiers = [options] include_package_data = True -packages = cunumbers +packages = + omninumeric + cyrillic python_requires = >=3.4 \ No newline at end of file diff --git a/tests/test_cunumbers.py b/tests/test_cyrillic.py similarity index 99% rename from tests/test_cunumbers.py rename to tests/test_cyrillic.py index 6aadbe7..a59a863 100644 --- a/tests/test_cunumbers.py +++ b/tests/test_cyrillic.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- import unittest -from cunumbers.cunumbers import * +from omninumeric.cyrillic import * class ToCUPlainTestCase(unittest.TestCase): From 1edba23ead1da6294412a4b091a94ed9ac6e8363 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sun, 21 Aug 2022 00:30:29 +0300 Subject: [PATCH 09/39] Update workflow --- .github/workflows/black.yml | 2 +- .github/workflows/test-python-3.5.yml | 4 ++-- .github/workflows/test-python-3.7.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 6bece3b..969d72c 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -4,7 +4,7 @@ on: pull_request: push: paths: - - 'cunumbers/**' + - 'omninumeric/**' - 'tests/**' jobs: diff --git a/.github/workflows/test-python-3.5.yml b/.github/workflows/test-python-3.5.yml index cfeae83..269cf54 100644 --- a/.github/workflows/test-python-3.5.yml +++ b/.github/workflows/test-python-3.5.yml @@ -4,12 +4,12 @@ on: push: branches: [ dev ] paths: - - 'cunumbers/**' + - 'omninumeric/**' - 'tests/**' pull_request: branches: [ dev ] paths: - - 'cunumbers/**' + - 'omninumeric/**' - 'tests/**' jobs: diff --git a/.github/workflows/test-python-3.7.yml b/.github/workflows/test-python-3.7.yml index 957ff55..a9f4e7b 100644 --- a/.github/workflows/test-python-3.7.yml +++ b/.github/workflows/test-python-3.7.yml @@ -4,12 +4,12 @@ on: push: branches: [ dev ] paths: - - 'cunumbers/**' + - 'omninumeric/**' - 'tests/**' pull_request: branches: [ dev ] paths: - - 'cunumbers/**' + - 'omninumeric/**' - 'tests/**' jobs: From a8cb16749c89e8d4c9774867a0a5d9d0a9821d09 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 03:51:36 +0300 Subject: [PATCH 10/39] All string concatenations as .format() --- omninumeric/cyrillic/cyrillic.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 01104c9..dc14471 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -61,16 +61,16 @@ def build(self): "Build the CU number from digit groups." for k in self.groups: - self.alphabetic = k + self.alphabetic + self.alphabetic = "{0}{1}".format(k, self.alphabetic) return self def wrapDot(self, cond_a, cond_b): "Prepend and/or append dots if appropriate flags are set." - self.alphabetic = ( - (self.dict.get("DOT") if cond_a else "") - + self.alphabetic - + (self.dict.get("DOT") if cond_b else "") + self.alphabetic = "{0}{1}{2}".format( + self.dict.get("DOT") if cond_a else "", + self.alphabetic, + self.dict.get("DOT") if cond_b else "", ) return self @@ -80,7 +80,7 @@ def delimDots(self, cond): if cond: for i, k in enumerate(self.groups[1:]): - self.groups[i + 1] = k + self.dict.get("DOT") + self.groups[i + 1] = "{0}{1}".format(k, self.dict.get("DOT")) return self @@ -95,8 +95,11 @@ def appendTitlo(self, cond): "\g<1>{0}\g<2>".format(self.dict.get("TITLO")), self.alphabetic, ) + self.alphabetic = ( - result[0] if result[1] > 0 else self.alphabetic + self.dict.get("TITLO") + result[0] + if result[1] > 0 + else "{0}{1}".format(self.alphabetic, self.dict.get("TITLO")) ) return self @@ -129,7 +132,7 @@ def appendThousandMarksDelim(cls, input, index): "Append thousand marks in delimeter style." if input: - return cls.dict.get("THOUSAND") * index + input + return "{0}{1}".format(cls.dict.get("THOUSAND") * index, input) else: return "" @@ -140,7 +143,7 @@ def appendThousandMarksPlain(cls, input, index): result = "" for i in input: - result = result + cls.appendThousandMarksDelim(i, index) + result = "{0}{1}".format(result, cls.appendThousandMarksDelim(i, index)) return result From e831081f2b33b780cea42e27807a6d11e738a392 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 04:27:21 +0300 Subject: [PATCH 11/39] Privatise --- omninumeric/__init__.py | 1 - omninumeric/cyrillic/__init__.py | 4 +- omninumeric/cyrillic/cyrillic.py | 228 +++++++++++++++---------------- omninumeric/omninumeric.py | 90 ++++++------ 4 files changed, 166 insertions(+), 157 deletions(-) diff --git a/omninumeric/__init__.py b/omninumeric/__init__.py index 477733c..77fe715 100644 --- a/omninumeric/__init__.py +++ b/omninumeric/__init__.py @@ -3,5 +3,4 @@ GreekTypeDictionary, ArabicNumberConverter, AlphabeticNumberConverter, - isinstance, ) diff --git a/omninumeric/cyrillic/__init__.py b/omninumeric/cyrillic/__init__.py index 17cfa1e..2b92c17 100644 --- a/omninumeric/cyrillic/__init__.py +++ b/omninumeric/cyrillic/__init__.py @@ -1,6 +1,6 @@ from .cyrillic import ( - to_alphabetic, - to_arabic, + ArabicNumber, + CyrillicNumber, to_cu, to_arab, CU_DELIM, diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index dc14471..99eb837 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -4,7 +4,11 @@ "Module for number conversion between Arabic and Cyrillic numeral systems." import re -from omninumeric import * +from omninumeric import ( + GreekTypeDictionary, + ArabicNumberConverter, + AlphabeticNumberConverter, +) CU_PLAIN = 0x1 # Write in plain style CU_DELIM = 0x10 # Read/write in delim style @@ -52,192 +56,197 @@ class CyrillicDictionary(GreekTypeDictionary): class ArabicNumber(ArabicNumberConverter): - dict = CyrillicDictionary + _dict = CyrillicDictionary def __init__(self, value, flags=0): super().__init__(value, flags) - def build(self): + def _build(self): "Build the CU number from digit groups." - for k in self.groups: - self.alphabetic = "{0}{1}".format(k, self.alphabetic) + for k in self._groups: + self._alphabetic = "{0}{1}".format(k, self._alphabetic) + return self - def wrapDot(self, cond_a, cond_b): + def _wrapDot(self, cond_a, cond_b): "Prepend and/or append dots if appropriate flags are set." - self.alphabetic = "{0}{1}{2}".format( - self.dict.get("DOT") if cond_a else "", - self.alphabetic, - self.dict.get("DOT") if cond_b else "", + self._alphabetic = "{0}{1}{2}".format( + self._dict.get("DOT") if cond_a else "", + self._alphabetic, + self._dict.get("DOT") if cond_b else "", ) return self - def delimDots(self, cond): + def _delimDots(self, cond): "Insert dots between digit 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")) + for i, k in enumerate(self._groups[1:]): + self._groups[i + 1] = "{0}{1}".format(k, self._dict.get("DOT")) return self - def appendTitlo(self, cond): + def _appendTitlo(self, cond): "Append titlo unless appropriate flag is set." if not cond: result = re.subn( "([\S]+)(?{0}\g<2>".format(self.dict.get("TITLO")), - self.alphabetic, + "\g<1>{0}\g<2>".format(self._dict.get("TITLO")), + self._alphabetic, ) - - self.alphabetic = ( + self._alphabetic = ( result[0] if result[1] > 0 - else "{0}{1}".format(self.alphabetic, self.dict.get("TITLO")) + else "{0}{1}".format(self._alphabetic, self._dict.get("TITLO")) ) return self - def swapDigits(self): + def _swapDigits(self): "Swap digits in 11-19." - 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 purgeEmptyGroups(self): + def _purgeEmptyGroups(self): "Remove empty groups from digit group collection." - for i, k in enumerate(self.groups): + for i, k in enumerate(self._groups): if not k: - self.groups.pop(i) + self._groups.pop(i) return self @classmethod - def appendThousandMarksDelim(cls, input, index): + def _appendThousandMarksDelim(cls, input, index): "Append thousand marks in delimeter style." if input: - return "{0}{1}".format(cls.dict.get("THOUSAND") * index, input) + return "{0}{1}".format(cls._dict.get("THOUSAND") * index, input) else: return "" @classmethod - def appendThousandMarksPlain(cls, input, index): + def _appendThousandMarksPlain(cls, input, index): "Append thousand marks in plain style." result = "" for i in input: - result = "{0}{1}".format(result, cls.appendThousandMarksDelim(i, index)) + result = "{0}{1}".format(result, cls._appendThousandMarksDelim(i, index)) return result - def appendThousandMarks(self, cond): + def _appendThousandMarks(self, cond): "Append thousand marks according to chosen style (plain or delimeter)." method = ( - self.appendThousandMarksDelim if cond else self.appendThousandMarksPlain + self._appendThousandMarksDelim if cond else self._appendThousandMarksPlain ) - for i, k in enumerate(self.groups): + for i, k in enumerate(self._groups): - self.groups[i] = method(self.groups[i], i) + self._groups[i] = method(self._groups[i], i) return self @classmethod - def getDigit(cls, input): + def _getDigit(cls, input): "Get CU digit for given Arabic digit." - return cls.dict.get(input) if input else "" + return cls._dict.get(input) if input else "" - def translateGroups(self): + def _translateGroups(self): "Translate the Arabic number per group." - for i, k in enumerate(self.groups): + for i, k in enumerate(self._groups): result = "" index = 0 while k > 0: - result = self.getDigit(k % 10 * pow(10, index)) + result + result = self._getDigit(k % 10 * pow(10, index)) + result index = index + 1 k = k // 10 - self.groups[i] = result + self._groups[i] = result return self - def ambiguityCheck(self, cond, flag): + def _ambiguityCheck(self, cond, flag): if 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 breakIntoGroups(self): + def _breakIntoGroups(self): "Break the Arabic number into groups of 3 digits." - while self.arabic > 0: - self.groups.append(self.arabic % 1000) - self.arabic = self.arabic // 1000 + while self._arabic > 0: + self._groups.append(self._arabic % 1000) + self._arabic = self._arabic // 1000 return self def convert(self): - "Convert the Arabic number to Cyrillic." - - return ( - self.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() - ) + """ + Convert an Arabic number into Cyrillic numeral system. Uses plain style by default. + + Requires a non-zero integer. + """ + + if super()._convert(): + return ( + self._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() + ) class CyrillicNumber(AlphabeticNumberConverter): - dict = CyrillicDictionary + _dict = CyrillicDictionary - 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( + _dict.get("THOUSAND"), + _dict.hundreds(), + _dict.tens(2), + _dict.digits(), + _dict.get(10), ) - def validate(self, regex): + def _validate(self): "Validate that input is a Cyrillic number." - if re.fullmatch(regex, self.alphabetic): + if re.fullmatch("{0}+".format(self._regex), self._alphabetic): return self else: raise ValueError( @@ -246,80 +255,69 @@ def validate(self, regex): def __init__(self, alphabetic): super().__init__(alphabetic) - self.validate("({0})+".format(self.regex)) + self._validate() - def prepare(self): + def _prepare(self): "Prepare the Cyrillic number for conversion." - if super().prepare(): - self.alphabetic = re.sub( - "[{0}\.]".format(self.dict.get("TITLO")), "", self.alphabetic + if super()._prepare(): + self._alphabetic = re.sub( + "[{0}\.]".format(self._dict.get("TITLO")), "", self._alphabetic ) # Strip ҃"҃ " and dots return self @classmethod - def calculateMultiplier(cls, index, input): + def _calculateMultiplier(cls, index, input): "Calculate multiplier for adjusting digit group value to its registry." multiplier = ( - re.match("({0}*)".format(cls.dict.get("THOUSAND")), input) + re.match("({0}*)".format(cls._dict.get("THOUSAND")), input) .groups()[0] - .count(cls.dict.get("THOUSAND")) + .count(cls._dict.get("THOUSAND")) ) # Count trailing thousand marks in the group multiplier = pow(1000, multiplier if multiplier else index - 1) # Use thousand marks if present, otherwise use group index return multiplier - def translateGroups(self): + def _translateGroups(self): "Translate the alphabetic number per group." - for i, k in enumerate(self.groups): + for i, k in enumerate(self._groups): - multiplier = self.calculateMultiplier(i, k) - k = re.sub(self.dict.get("THOUSAND"), "", k) # Strip thousand marks - self.arabic += self.translate(k) * multiplier + multiplier = self._calculateMultiplier(i, k) + k = re.sub(self._dict.get("THOUSAND"), "", k) # Strip thousand marks + self._arabic += self._translate(k) * multiplier return self - def breakIntoGroups(self, regex): + def _breakIntoGroups(self): "Break the Cyrillic number in groups of 1-3 digits." - self.groups = re.split(regex, self.alphabetic) # Break into groups - for i, k in enumerate(self.groups): - self.groups.pop(i) if not k else True # Purge empty groups - self.groups.reverse() # Reverse groups (to ascending order) + self._groups = re.split(self._regex, self._alphabetic) # Break into groups + for i, k in enumerate(self._groups): + self._groups.pop(i) if not k else True # Purge empty groups + self._groups.reverse() # Reverse groups (to ascending order) return self def convert(self): - "Convert the Cyrillic number to Arabic." - - return self.breakIntoGroups("({0})".format(self.regex)).translateGroups().get() - - -def to_alphabetic(input, flags=0): - """ - Convert an Arabic number into Cyrillic numeral system. Uses plain style by default. - - Requires a non-zero integer. - """ - - if isinstance(input, int, "Non-zero integer required, got {0}"): - return ArabicNumber(input, flags).convert() + """ + Convert a Cyrillic number into Arabic numeral system. + Requires a non-empty string. + """ -to_cu = to_alphabetic + if super()._convert(): + return self._breakIntoGroups()._translateGroups()._get() -def to_arabic(input, flags=0): - """ - Convert a Cyrillic number into Arabic numeral system. +def to_cu(integer, flags=0): + "Deprecated; use ArabicNumber().convert() instead." - Requires a non-empty string. - """ + return ArabicNumber(integer, flags).convert() - if isinstance(input, str, "Non-empty string required, got {0}"): - return CyrillicNumber(input).convert() +def to_arab(alphabetic, flags=0): + "Deprecated; use CyrillicNumber().convert() instead." -to_arab = to_arabic + return CyrillicNumber(alphabetic).convert() diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index a8c9667..9bebec1 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -4,6 +4,14 @@ from enum import Enum, unique +def isinstance(value, condition, msg): + t = type(value) + if t == condition: + return True + else: + raise TypeError(msg.format(t)) + + class Dictionary(Enum): @classmethod def get(cls, input): @@ -18,7 +26,7 @@ def get(cls, input): class GreekTypeDictionary(Dictionary): @classmethod - def __getmany(cls, start=1, end=10, step=1): + def _getmany(cls, start=1, end=10, step=1): r = "" for i in range(start * step, end * step, step): r += cls(i).name @@ -26,85 +34,89 @@ def __getmany(cls, start=1, end=10, step=1): @classmethod def digits(cls, start=1, end=10): - return cls.__getmany(start, end, 1) + return cls._getmany(start, end, 1) @classmethod def tens(cls, start=1, end=10): - return cls.__getmany(start, end, 10) + return cls._getmany(start, end, 10) @classmethod def hundreds(cls, start=1, end=10): - return cls.__getmany(start, end, 100) + return cls._getmany(start, end, 100) -class ArabicNumberConverter: +class NumberConverter: + def _convert(self, value, condition, msg): + if isinstance(value, condition, msg): + return self - dict = Dictionary - def prepare(self): +class ArabicNumberConverter(NumberConverter): + def _prepare(self): "Prepare the Arabic number for conversion." - if self.arabic <= 0: + if self._arabic <= 0: raise ValueError("Non-zero integer required") def __init__(self, value, flags=0): - self.alphabetic = "" - self.arabic = value - self.flags = flags - self.groups = [] - self.prepare() + self._alphabetic = "" + self._arabic = value + self._flags = flags + self._groups = [] + self._prepare() - def get(self): + def _get(self): "Return the alphabetic number representation." - return self.alphabetic + return self._alphabetic - def hasFlag(self, flag): + def _hasFlag(self, flag): "Check if a flag is set." - return self.flags & flag - # return False if self.flags & flag == 0 else True + return self._flags & flag + # return False if self._flags & flag == 0 else True + def _convert(self): + if super()._convert(self._arabic, int, "Non-zero integer required, got {0}"): + return self -class AlphabeticNumberConverter: - dict = Dictionary +class AlphabeticNumberConverter(NumberConverter): - def prepare(self): + _dict = Dictionary + + def _prepare(self): "Prepare the alphabetic number for conversion." - if self.alphabetic: - self.alphabetic = str.lower(str.strip(self.alphabetic)) - # self.alphabetic = str.lower(self.alphabetic) + if self._alphabetic: + self._alphabetic = str.lower(str.strip(self._alphabetic)) return self else: raise ValueError("Non-empty string required") def __init__(self, alphabetic): - self.alphabetic = alphabetic - self.arabic = 0 - self.groups = [] - self.prepare() + self._alphabetic = alphabetic + self._arabic = 0 + self._groups = [] + self._prepare() - def get(self): + def _get(self): "Return the Arabic number representation." - return self.arabic + return self._arabic @classmethod - def translate(cls, alphabetic): + def _translate(cls, alphabetic): total = 0 # Current group total value for k in alphabetic: - total += cls.dict.get(k) + total += cls._dict.get(k) return total - -def isinstance(input, condition, msg): - t = type(input) - if t == condition: - return True - else: - raise TypeError(msg.format(t)) + def _convert(self): + if super()._convert( + self._alphabetic, str, "Non-empty string required, got {0}" + ): + return self From 86fd1ebd36d6f11e21e17738d1de1c796cfb7efa Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 06:20:26 +0300 Subject: [PATCH 12/39] Cyrillic as Greek derivation Detached Greek style conversion operations in greek.py module Cyrillic.py now relies on greek.py greek.py is WIP Minor fixes --- omninumeric/__init__.py | 1 - omninumeric/cyrillic/cyrillic.py | 221 +++++++------------------------ omninumeric/greek/__init__.py | 7 + omninumeric/greek/greek.py | 155 ++++++++++++++++++++++ omninumeric/greek/old/old.py | 81 +++++++++++ omninumeric/omninumeric.py | 65 ++++----- 6 files changed, 319 insertions(+), 211 deletions(-) create mode 100644 omninumeric/greek/__init__.py create mode 100644 omninumeric/greek/greek.py create mode 100644 omninumeric/greek/old/old.py diff --git a/omninumeric/__init__.py b/omninumeric/__init__.py index 77fe715..84a696d 100644 --- a/omninumeric/__init__.py +++ b/omninumeric/__init__.py @@ -1,6 +1,5 @@ from .omninumeric import ( Dictionary, - GreekTypeDictionary, ArabicNumberConverter, AlphabeticNumberConverter, ) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 99eb837..63a8ba2 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -4,14 +4,9 @@ "Module for number conversion between Arabic and Cyrillic numeral systems." import re -from omninumeric import ( - GreekTypeDictionary, - ArabicNumberConverter, - AlphabeticNumberConverter, -) - -CU_PLAIN = 0x1 # Write in plain style -CU_DELIM = 0x10 # Read/write in delim style +from omninumeric.greek import * + + CU_NOTITLO = 0x100 # DO NOT append titlo CU_ENDDOT = 0x1000 # Append dot CU_PREDOT = 0x10000 # Prepend dot @@ -21,7 +16,7 @@ CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots -class CyrillicDictionary(GreekTypeDictionary): +class _CyrillicDictionary(DictionaryGreek): а = 1 в = 2 @@ -55,37 +50,31 @@ class CyrillicDictionary(GreekTypeDictionary): DOT = "." # Dot decorator -class ArabicNumber(ArabicNumberConverter): - _dict = CyrillicDictionary - - def __init__(self, value, flags=0): - super().__init__(value, flags) - - def _build(self): - "Build the CU number from digit groups." - - for k in self._groups: - self._alphabetic = "{0}{1}".format(k, self._alphabetic) - - return self - - def _wrapDot(self, cond_a, cond_b): - "Prepend and/or append dots if appropriate flags are set." +class ArabicNumber(ArabicNumberConverterGreek): + _dict = _CyrillicDictionary - self._alphabetic = "{0}{1}{2}".format( - self._dict.get("DOT") if cond_a else "", - self._alphabetic, - self._dict.get("DOT") if cond_b else "", - ) + def _ambiguityCheck(self, cond, flag): + if cond: + try: + if (self._groups[0] // 10 % 10 == 1) and ( + self._groups[1] // 10 % 10 == 0 + ): + self._flags = self._flags | flag + finally: + return self + else: + return self - return self + def _swapDigits(self): + "Swap digits in 11-19." - def _delimDots(self, cond): - "Insert dots between digit groups if appropriate flag is set." + for i, k in enumerate(self._groups): - if cond: - for i, k in enumerate(self._groups[1:]): - self._groups[i + 1] = "{0}{1}".format(k, self._dict.get("DOT")) + self._groups[i] = re.sub( + "({0})([{1}])".format(self._dict.get(10), self._dict.digits()), + "\g<2>\g<1>", + self._groups[i], + ) return self @@ -108,104 +97,24 @@ def _appendTitlo(self, cond): return self - def _swapDigits(self): - "Swap digits in 11-19." - - for i, k in enumerate(self._groups): - - self._groups[i] = re.sub( - "({0})([{1}])".format(self._dict.get(10), self._dict.digits()), - "\g<2>\g<1>", - self._groups[i], - ) - - return self - - def _purgeEmptyGroups(self): - "Remove empty groups from digit group collection." - - for i, k in enumerate(self._groups): + def _delimDots(self, cond): + "Insert dots between digit groups if appropriate flag is set." - if not k: - self._groups.pop(i) + if cond: + for i, k in enumerate(self._groups[1:]): + self._groups[i + 1] = "{0}{1}".format(k, self._dict.get("DOT")) return self - @classmethod - def _appendThousandMarksDelim(cls, input, index): - "Append thousand marks in delimeter style." - - if input: - return "{0}{1}".format(cls._dict.get("THOUSAND") * index, input) - else: - return "" - - @classmethod - def _appendThousandMarksPlain(cls, input, index): - "Append thousand marks in plain style." - - result = "" - - for i in input: - result = "{0}{1}".format(result, cls._appendThousandMarksDelim(i, index)) - - return result - - def _appendThousandMarks(self, cond): - "Append thousand marks according to chosen style (plain or delimeter)." + def _wrapDot(self, cond_a, cond_b): + "Prepend and/or append dots if appropriate flags are set." - method = ( - self._appendThousandMarksDelim if cond else self._appendThousandMarksPlain + self._alphabetic = "{0}{1}{2}".format( + self._dict.get("DOT") if cond_a else "", + self._alphabetic, + self._dict.get("DOT") if cond_b else "", ) - for i, k in enumerate(self._groups): - - self._groups[i] = method(self._groups[i], i) - - return self - - @classmethod - def _getDigit(cls, input): - "Get CU digit for given Arabic digit." - - return cls._dict.get(input) if input else "" - - def _translateGroups(self): - "Translate the Arabic number per group." - - for i, k in enumerate(self._groups): - - result = "" - index = 0 - - while k > 0: - result = self._getDigit(k % 10 * pow(10, index)) + result - index = index + 1 - k = k // 10 - - self._groups[i] = result - - return self - - def _ambiguityCheck(self, cond, flag): - if cond: - try: - if (self._groups[0] // 10 % 10 == 1) and ( - self._groups[1] // 10 % 10 == 0 - ): - self._flags = self._flags | flag - finally: - return self - else: - return self - - def _breakIntoGroups(self): - "Break the Arabic number into groups of 3 digits." - - while self._arabic > 0: - self._groups.append(self._arabic % 1000) - self._arabic = self._arabic // 1000 - return self def convert(self): @@ -215,7 +124,7 @@ def convert(self): Requires a non-zero integer. """ - if super()._convert(): + if super().convert(): return ( self._breakIntoGroups() ._ambiguityCheck(self._hasFlag(CU_DELIM), CU_DOT) @@ -231,9 +140,9 @@ def convert(self): ) -class CyrillicNumber(AlphabeticNumberConverter): +class CyrillicNumber(AlphabeticNumberConverterGreek): - _dict = CyrillicDictionary + _dict = _CyrillicDictionary _regex = "({0}*[{1}]?(?:(?:{0}*[{3}])?{4}|(?:{0}*[{2}])?(?:{0}*[{3}])?))".format( _dict.get("THOUSAND"), @@ -243,20 +152,6 @@ class CyrillicNumber(AlphabeticNumberConverter): _dict.get(10), ) - def _validate(self): - "Validate that input is a Cyrillic number." - - if re.fullmatch("{0}+".format(self._regex), self._alphabetic): - return self - else: - raise ValueError( - "String does not match any pattern for Cyrillic numeral system number" - ) - - def __init__(self, alphabetic): - super().__init__(alphabetic) - self._validate() - def _prepare(self): "Prepare the Cyrillic number for conversion." @@ -266,39 +161,17 @@ def _prepare(self): ) # Strip ҃"҃ " and dots return self - @classmethod - def _calculateMultiplier(cls, index, input): - "Calculate multiplier for adjusting digit group value to its registry." - - multiplier = ( - re.match("({0}*)".format(cls._dict.get("THOUSAND")), input) - .groups()[0] - .count(cls._dict.get("THOUSAND")) - ) # Count trailing thousand marks in the group - multiplier = pow(1000, multiplier if multiplier else index - 1) - # Use thousand marks if present, otherwise use group index - return multiplier - - def _translateGroups(self): - "Translate the alphabetic number per group." - - for i, k in enumerate(self._groups): + def _validate(self): + "Validate that input is a Cyrillic number." - multiplier = self._calculateMultiplier(i, k) - k = re.sub(self._dict.get("THOUSAND"), "", k) # Strip thousand marks - self._arabic += self._translate(k) * multiplier + super()._validate("{0}+".format(self._regex)) - return self + def __init__(self, alphabetic): + super().__init__(alphabetic) + self._prepare()._validate() def _breakIntoGroups(self): - "Break the Cyrillic number in groups of 1-3 digits." - - self._groups = re.split(self._regex, self._alphabetic) # Break into groups - for i, k in enumerate(self._groups): - self._groups.pop(i) if not k else True # Purge empty groups - self._groups.reverse() # Reverse groups (to ascending order) - - return self + return super()._breakIntoGroups(self._regex) def convert(self): """ @@ -307,7 +180,7 @@ def convert(self): Requires a non-empty string. """ - if super()._convert(): + if super().convert(): return self._breakIntoGroups()._translateGroups()._get() diff --git a/omninumeric/greek/__init__.py b/omninumeric/greek/__init__.py new file mode 100644 index 0000000..25d550d --- /dev/null +++ b/omninumeric/greek/__init__.py @@ -0,0 +1,7 @@ +from .greek import ( + DictionaryGreek, + ArabicNumberConverterGreek, + AlphabeticNumberConverterGreek, + CU_PLAIN, + CU_DELIM, +) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py new file mode 100644 index 0000000..fa345d1 --- /dev/null +++ b/omninumeric/greek/greek.py @@ -0,0 +1,155 @@ +# -*- coding: UTF-8 -*- +# For licensing information see LICENSE file included in the project's root directory. + +import re + +from omninumeric import ( + Dictionary, + ArabicNumberConverter, + AlphabeticNumberConverter, +) + + +CU_PLAIN = 0x1 # Write in plain style +CU_DELIM = 0x10 # Read/write in delim style + + +class DictionaryGreek(Dictionary): + @classmethod + def _getmany(cls, start=1, end=10, step=1): + r = "" + for i in range(start * step, end * step, step): + r += cls(i).name + return r + + @classmethod + def digits(cls, start=1, end=10): + return cls._getmany(start, end, 1) + + @classmethod + def tens(cls, start=1, end=10): + return cls._getmany(start, end, 10) + + @classmethod + def hundreds(cls, start=1, end=10): + return cls._getmany(start, end, 100) + + +class ArabicNumberConverterGreek(ArabicNumberConverter): + def _build(self): + "Build the CU number from digit groups." + + for k in self._groups: + self._alphabetic = "{0}{1}".format(k, self._alphabetic) + return self + + def _purgeEmptyGroups(self): + "Remove empty groups from digit group collection." + + for i, k in enumerate(self._groups): + + if not k: + self._groups.pop(i) + + return self + + @classmethod + def _appendThousandMarksDelim(cls, input, index): + "Append thousand marks in delimeter style." + + if input: + return "{0}{1}".format(cls._dict.get("THOUSAND") * index, input) + else: + return "" + + @classmethod + def _appendThousandMarksPlain(cls, input, index): + "Append thousand marks in plain style." + + result = "" + + for i in input: + result = "{0}{1}".format(result, cls._appendThousandMarksDelim(i, index)) + + return result + + def _appendThousandMarks(self, cond): + "Append thousand marks according to chosen style (plain or delimeter)." + + method = ( + self._appendThousandMarksDelim if cond else self._appendThousandMarksPlain + ) + + for i, k in enumerate(self._groups): + + self._groups[i] = method(self._groups[i], i) + + return self + + @classmethod + def _getDigit(cls, input): + "Get CU digit for given Arabic digit." + + return cls._dict.get(input) if input else "" + + def _translateGroups(self): + "Translate the Arabic number per group." + + for i, k in enumerate(self._groups): + + result = "" + index = 0 + + while k > 0: + result = self._getDigit(k % 10 * pow(10, index)) + result + index = index + 1 + k = k // 10 + + self._groups[i] = result + + return self + + def _breakIntoGroups(self): + "Break the Arabic number into groups of 3 digits." + + while self._arabic > 0: + self._groups.append(self._arabic % 1000) + self._arabic = self._arabic // 1000 + + return self + + +class AlphabeticNumberConverterGreek(AlphabeticNumberConverter): + @classmethod + def _calculateMultiplier(cls, index, input): + "Calculate multiplier for adjusting digit group value to its registry." + + multiplier = ( + re.match("({0}*)".format(cls._dict.get("THOUSAND")), input) + .groups()[0] + .count(cls._dict.get("THOUSAND")) + ) # Count trailing thousand marks in the group + multiplier = pow(1000, multiplier if multiplier else index - 1) + # Use thousand marks if present, otherwise use group index + return multiplier + + def _translateGroups(self): + "Translate the alphabetic number per group." + + for i, k in enumerate(self._groups): + + multiplier = self._calculateMultiplier(i, k) + k = re.sub(self._dict.get("THOUSAND"), "", k) # Strip thousand marks + self._arabic += self._translate(k) * multiplier + + return self + + def _breakIntoGroups(self, regex=""): + "Break the Cyrillic number in groups of 1-3 digits." + + self._groups = re.split(regex, self._alphabetic) # Break into groups + for i, k in enumerate(self._groups): + self._groups.pop(i) if not k else True # Purge empty groups + self._groups.reverse() # Reverse groups (to ascending order) + + return self diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py new file mode 100644 index 0000000..85a3e86 --- /dev/null +++ b/omninumeric/greek/old/old.py @@ -0,0 +1,81 @@ +# -*- coding: UTF-8 -*- +# For licensing information see LICENSE file included in the project's root directory. + + +from omninumeric import ( + ArabicNumberConverter, + AlphabeticNumberConverter, +) +from omninumeric.greek import * + + +class _OldGreekDictionary(DictionaryGreek): + + α = 1 + β = 2 + γ = 3 + δ = 4 + є = 5 + ϛ = 6 + ζ = 7 + η = 8 + θ = 9 + ι = 10 + κ = 20 + λ = 30 + μ = 40 + ν = 50 + ξ = 60 + ο = 70 + π = 80 + ϟ = 90 # ϙ + ρ = 100 + σ = 200 + τ = 300 + υ = 400 + φ = 500 + χ = 600 + ψ = 700 + ω = 800 + ϡ = 900 + THOUSAND = "͵" # Thousand mark + KERAIA = "ʹ" # "Keraia" decorator + OVERLINE = "̅" # Overline decorator + DOT = "." # Dot decorator + + +class ArabicNumber(ArabicNumberConverterGreek): + + _dict = _OldGreekDictionary + + def convert(self): + """ + Convert an Arabic number into Cyrillic numeral system. Uses plain style by default. + + Requires a non-zero integer. + """ + + if super().convert(): + return ( + self._breakIntoGroups() + ._translateGroups() + ._appendThousandMarks(self._hasFlag(CU_DELIM)) + ._purgeEmptyGroups() + ._build() + ._get() + ) + + +class OldGreekNumber(AlphabeticNumberConverterGreek): + + _dict = _OldGreekDictionary + + def convert(self): + """ + Convert a Cyrillic number into Arabic numeral system. + + Requires a non-empty string. + """ + + if super().convert(): + return self._breakIntoGroups()._translateGroups()._get() diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 9bebec1..e7a71fa 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*- # For licensing information see LICENSE file included in the project's root directory. +import re from enum import Enum, unique @@ -12,6 +13,7 @@ def isinstance(value, condition, msg): raise TypeError(msg.format(t)) +@unique class Dictionary(Enum): @classmethod def get(cls, input): @@ -24,46 +26,28 @@ def get(cls, input): return "" -class GreekTypeDictionary(Dictionary): - @classmethod - def _getmany(cls, start=1, end=10, step=1): - r = "" - for i in range(start * step, end * step, step): - r += cls(i).name - return r - - @classmethod - def digits(cls, start=1, end=10): - return cls._getmany(start, end, 1) - - @classmethod - def tens(cls, start=1, end=10): - return cls._getmany(start, end, 10) - - @classmethod - def hundreds(cls, start=1, end=10): - return cls._getmany(start, end, 100) - - class NumberConverter: - def _convert(self, value, condition, msg): + _dict = NotImplemented + + def convert(self, value, condition, msg): if isinstance(value, condition, msg): - return self + return NotImplemented class ArabicNumberConverter(NumberConverter): - def _prepare(self): - "Prepare the Arabic number for conversion." + def _validate(self): + "Validate that input is a natural Arabic number." if self._arabic <= 0: - raise ValueError("Non-zero integer required") + raise ValueError("Natural number integer required") + return self def __init__(self, value, flags=0): self._alphabetic = "" self._arabic = value self._flags = flags self._groups = [] - self._prepare() + self._validate() def _get(self): "Return the alphabetic number representation." @@ -76,14 +60,14 @@ def _hasFlag(self, flag): return self._flags & flag # return False if self._flags & flag == 0 else True - def _convert(self): - if super()._convert(self._arabic, int, "Non-zero integer required, got {0}"): - return self + def convert(self): + if super().convert(self._arabic, int, "Non-zero integer required, got {0}"): + return NotImplemented class AlphabeticNumberConverter(NumberConverter): - _dict = Dictionary + _regex = NotImplemented def _prepare(self): "Prepare the alphabetic number for conversion." @@ -94,12 +78,23 @@ def _prepare(self): else: raise ValueError("Non-empty string required") + def _validate(self, regex=""): + "Validate that input is a alphabetic number in appropriate writing system." + + if re.fullmatch(regex, self._alphabetic): + return NotImplemented + else: + raise ValueError( + "String does not match any pattern for Cyrillic numeral system number" + ) + def __init__(self, alphabetic): self._alphabetic = alphabetic self._arabic = 0 self._groups = [] self._prepare() + return NotImplemented def _get(self): "Return the Arabic number representation." @@ -115,8 +110,6 @@ def _translate(cls, alphabetic): return total - def _convert(self): - if super()._convert( - self._alphabetic, str, "Non-empty string required, got {0}" - ): - return self + def convert(self): + if super().convert(self._alphabetic, str, "Non-empty string required, got {0}"): + return NotImplemented From 309a1b190c2b99cb26b02fe6c347c9484616fad5 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 21:35:51 +0300 Subject: [PATCH 13/39] Move flags to top-level converter class --- omninumeric/omninumeric.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index e7a71fa..1e7cc96 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -6,6 +6,7 @@ def isinstance(value, condition, msg): + t = type(value) if t == condition: return True @@ -29,7 +30,19 @@ def get(cls, input): class NumberConverter: _dict = NotImplemented + def __init__(self, value=0, flags=0): + + self._flags = flags + return NotImplemented + + def _hasFlag(self, flag): + "Check if a flag is set." + + return self._flags & flag + # return False if self._flags & flag == 0 else True + def convert(self, value, condition, msg): + if isinstance(value, condition, msg): return NotImplemented @@ -43,9 +56,10 @@ def _validate(self): return self def __init__(self, value, flags=0): + + super().__init__(value, flags) self._alphabetic = "" self._arabic = value - self._flags = flags self._groups = [] self._validate() @@ -54,12 +68,6 @@ def _get(self): return self._alphabetic - def _hasFlag(self, flag): - "Check if a flag is set." - - return self._flags & flag - # return False if self._flags & flag == 0 else True - def convert(self): if super().convert(self._arabic, int, "Non-zero integer required, got {0}"): return NotImplemented @@ -88,8 +96,9 @@ def _validate(self, regex=""): "String does not match any pattern for Cyrillic numeral system number" ) - def __init__(self, alphabetic): + def __init__(self, alphabetic, flags=0): + super().__init__(alphabetic, flags) self._alphabetic = alphabetic self._arabic = 0 self._groups = [] @@ -111,5 +120,6 @@ def _translate(cls, alphabetic): return total def convert(self): + if super().convert(self._alphabetic, str, "Non-empty string required, got {0}"): return NotImplemented From 51c23f06aad27c5c2f71d26d1088f8486a88bc16 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 21:38:31 +0300 Subject: [PATCH 14/39] Define exports --- omninumeric/__init__.py | 2 ++ omninumeric/cyrillic/__init__.py | 23 +++++++++++++++++++---- omninumeric/greek/__init__.py | 12 ++++++++++-- omninumeric/greek/old/__init__.py | 3 +++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 omninumeric/greek/old/__init__.py diff --git a/omninumeric/__init__.py b/omninumeric/__init__.py index 84a696d..bc57f39 100644 --- a/omninumeric/__init__.py +++ b/omninumeric/__init__.py @@ -3,3 +3,5 @@ ArabicNumberConverter, AlphabeticNumberConverter, ) + +__all__ = ["Dictionary", "ArabicNumberConverter", "AlphabeticNumberConverter"] diff --git a/omninumeric/cyrillic/__init__.py b/omninumeric/cyrillic/__init__.py index 2b92c17..183c143 100644 --- a/omninumeric/cyrillic/__init__.py +++ b/omninumeric/cyrillic/__init__.py @@ -1,12 +1,27 @@ from .cyrillic import ( - ArabicNumber, - CyrillicNumber, - to_cu, - to_arab, + CU_PLAIN, CU_DELIM, CU_NOTITLO, CU_ENDDOT, CU_DELIMDOT, CU_WRAPDOT, CU_ALLDOT, + ArabicNumber, + CyrillicNumber, + to_cu, + to_arab, ) + +__all__ = [ + "ArabicNumber", + "CyrillicNumber", + "to_cu", + "to_arab", + "CU_PLAIN", + "CU_DELIM", + "CU_NOTITLO", + "CU_ENDDOT", + "CU_DELIMDOT", + "CU_WRAPDOT", + "CU_ALLDOT", +] diff --git a/omninumeric/greek/__init__.py b/omninumeric/greek/__init__.py index 25d550d..ba27287 100644 --- a/omninumeric/greek/__init__.py +++ b/omninumeric/greek/__init__.py @@ -1,7 +1,15 @@ from .greek import ( + CU_PLAIN, + CU_DELIM, DictionaryGreek, ArabicNumberConverterGreek, AlphabeticNumberConverterGreek, - CU_PLAIN, - CU_DELIM, ) + +__all__ = [ + "CU_PLAIN", + "CU_DELIM", + "DictionaryGreek", + "ArabicNumberConverterGreek", + "AlphabeticNumberConverterGreek", +] diff --git a/omninumeric/greek/old/__init__.py b/omninumeric/greek/old/__init__.py new file mode 100644 index 0000000..d4bd9d0 --- /dev/null +++ b/omninumeric/greek/old/__init__.py @@ -0,0 +1,3 @@ +from .old import ArabicNumber, OldGreekNumber + +__all__ = ["ArabicNumber", "OldGreekNumber"] From dca4165844d1f4ffcbc33cb4c85e5c76a1a22222 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:35:23 +0300 Subject: [PATCH 15/39] Flag adjustments --- omninumeric/cyrillic/cyrillic.py | 10 ++++++---- omninumeric/greek/__init__.py | 8 ++++---- omninumeric/greek/greek.py | 4 ++-- omninumeric/greek/old/old.py | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 63a8ba2..d50e8cf 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -7,10 +7,12 @@ from omninumeric.greek import * -CU_NOTITLO = 0x100 # DO NOT append titlo -CU_ENDDOT = 0x1000 # Append dot -CU_PREDOT = 0x10000 # Prepend dot -CU_DOT = 0x100000 # Delimeter dots (private, for internal flag checks) +CU_PLAIN = PLAIN +CU_DELIM = DELIM +CU_NOTITLO = 0b10 # DO NOT append titlo +CU_ENDDOT = 0b100 # Append dot +CU_PREDOT = 0b1000 # Prepend dot +CU_DOT = 0b10000 # Delimeter dots (private, for internal flag checks) CU_DELIMDOT = CU_DOT | CU_DELIM # Delimeter dots (forces delim style) CU_WRAPDOT = CU_ENDDOT | CU_PREDOT # Wrap in dots CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots diff --git a/omninumeric/greek/__init__.py b/omninumeric/greek/__init__.py index ba27287..71ac4f4 100644 --- a/omninumeric/greek/__init__.py +++ b/omninumeric/greek/__init__.py @@ -1,14 +1,14 @@ from .greek import ( - CU_PLAIN, - CU_DELIM, + PLAIN, + DELIM, DictionaryGreek, ArabicNumberConverterGreek, AlphabeticNumberConverterGreek, ) __all__ = [ - "CU_PLAIN", - "CU_DELIM", + "PLAIN", + "DELIM", "DictionaryGreek", "ArabicNumberConverterGreek", "AlphabeticNumberConverterGreek", diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index fa345d1..e8b6e82 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -10,8 +10,8 @@ ) -CU_PLAIN = 0x1 # Write in plain style -CU_DELIM = 0x10 # Read/write in delim style +PLAIN = 0 # Write in plain style +DELIM = 0b1 # Read/write in delim style class DictionaryGreek(Dictionary): diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index 85a3e86..0c9d36d 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -59,7 +59,7 @@ def convert(self): return ( self._breakIntoGroups() ._translateGroups() - ._appendThousandMarks(self._hasFlag(CU_DELIM)) + ._appendThousandMarks(self._hasFlag(DELIM)) ._purgeEmptyGroups() ._build() ._get() From 9abc59b556dbbc7b2ad6ee894198753d36667f3f Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Wed, 17 Aug 2022 23:46:06 +0300 Subject: [PATCH 16/39] Streamline convert() method isinstance() calls moved to _validate() --- omninumeric/cyrillic/cyrillic.py | 34 +++++++++++++------------------- omninumeric/greek/old/old.py | 21 +++++++++----------- omninumeric/omninumeric.py | 27 +++++++++---------------- 3 files changed, 32 insertions(+), 50 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index d50e8cf..e54aa37 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -126,20 +126,19 @@ def convert(self): Requires a non-zero integer. """ - if super().convert(): - return ( - self._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() - ) + return ( + self._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() + ) class CyrillicNumber(AlphabeticNumberConverterGreek): @@ -168,10 +167,6 @@ def _validate(self): super()._validate("{0}+".format(self._regex)) - def __init__(self, alphabetic): - super().__init__(alphabetic) - self._prepare()._validate() - def _breakIntoGroups(self): return super()._breakIntoGroups(self._regex) @@ -182,8 +177,7 @@ def convert(self): Requires a non-empty string. """ - if super().convert(): - return self._breakIntoGroups()._translateGroups()._get() + return self._breakIntoGroups()._translateGroups()._get() def to_cu(integer, flags=0): diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index 0c9d36d..3ac8295 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -54,16 +54,14 @@ def convert(self): Requires a non-zero integer. """ - - if super().convert(): - return ( - self._breakIntoGroups() - ._translateGroups() - ._appendThousandMarks(self._hasFlag(DELIM)) - ._purgeEmptyGroups() - ._build() - ._get() - ) + return ( + self._breakIntoGroups() + ._translateGroups() + ._appendThousandMarks(self._hasFlag(DELIM)) + ._purgeEmptyGroups() + ._build() + ._get() + ) class OldGreekNumber(AlphabeticNumberConverterGreek): @@ -77,5 +75,4 @@ def convert(self): Requires a non-empty string. """ - if super().convert(): - return self._breakIntoGroups()._translateGroups()._get() + return self._breakIntoGroups()._translateGroups()._get() diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 1e7cc96..0e42fb0 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -5,12 +5,10 @@ from enum import Enum, unique -def isinstance(value, condition, msg): +def isinstance(value, cond, msg): t = type(value) - if t == condition: - return True - else: + if not t == cond: raise TypeError(msg.format(t)) @@ -41,16 +39,17 @@ def _hasFlag(self, flag): return self._flags & flag # return False if self._flags & flag == 0 else True - def convert(self, value, condition, msg): + def convert(self): - if isinstance(value, condition, msg): - return NotImplemented + return NotImplemented class ArabicNumberConverter(NumberConverter): def _validate(self): "Validate that input is a natural Arabic number." + isinstance(self._arabic, int, "Non-zero integer required, got {0}") + if self._arabic <= 0: raise ValueError("Natural number integer required") return self @@ -68,10 +67,6 @@ def _get(self): return self._alphabetic - def convert(self): - if super().convert(self._arabic, int, "Non-zero integer required, got {0}"): - return NotImplemented - class AlphabeticNumberConverter(NumberConverter): @@ -89,6 +84,8 @@ def _prepare(self): def _validate(self, regex=""): "Validate that input is a alphabetic number in appropriate writing system." + isinstance(self._alphabetic, str, "Non-empty string required, got {0}") + if re.fullmatch(regex, self._alphabetic): return NotImplemented else: @@ -102,8 +99,7 @@ def __init__(self, alphabetic, flags=0): self._alphabetic = alphabetic self._arabic = 0 self._groups = [] - self._prepare() - return NotImplemented + self._prepare()._validate() def _get(self): "Return the Arabic number representation." @@ -118,8 +114,3 @@ def _translate(cls, alphabetic): total += cls._dict.get(k) return total - - def convert(self): - - if super().convert(self._alphabetic, str, "Non-empty string required, got {0}"): - return NotImplemented From 178c72129caf36bd6e07937a4e1402f7a517ebe8 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Thu, 18 Aug 2022 00:30:45 +0300 Subject: [PATCH 17/39] ABC logic adjustments Refactor _prepare() and _validate() logic More power to NumberConverter.__init__() --- omninumeric/cyrillic/cyrillic.py | 27 ++++++++---- omninumeric/omninumeric.py | 71 +++++++++++++------------------- 2 files changed, 47 insertions(+), 51 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index e54aa37..c0a2ea9 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -127,7 +127,8 @@ def convert(self): """ return ( - self._breakIntoGroups() + self._validate() + ._breakIntoGroups() ._ambiguityCheck(self._hasFlag(CU_DELIM), CU_DOT) ._translateGroups() ._appendThousandMarks(self._hasFlag(CU_DELIM)) @@ -156,16 +157,24 @@ class CyrillicNumber(AlphabeticNumberConverterGreek): def _prepare(self): "Prepare the Cyrillic number for conversion." - if super()._prepare(): - self._alphabetic = re.sub( - "[{0}\.]".format(self._dict.get("TITLO")), "", self._alphabetic - ) # Strip ҃"҃ " and dots - return self + super()._prepare() + self._alphabetic = re.sub( + "[{0}\{1}]".format(self._dict.get("TITLO"), self._dict.get("DOT")), + "", + self._alphabetic, + ) # Strip ҃decorators + + return self def _validate(self): - "Validate that input is a Cyrillic number." - super()._validate("{0}+".format(self._regex)) + super()._validate() + if not re.fullmatch("{0}+".format(self._regex), self._alphabetic): + raise ValueError( + "String does not match any pattern for Cyrillic numeral system number" + ) + + return self def _breakIntoGroups(self): return super()._breakIntoGroups(self._regex) @@ -177,7 +186,7 @@ def convert(self): Requires a non-empty string. """ - return self._breakIntoGroups()._translateGroups()._get() + return self._prepare()._validate()._breakIntoGroups()._translateGroups()._get() def to_cu(integer, flags=0): diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 0e42fb0..9f78f41 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -28,10 +28,12 @@ def get(cls, input): class NumberConverter: _dict = NotImplemented - def __init__(self, value=0, flags=0): + def __init__(self, flags=0): + self._arabic = 0 + self._alphabetic = "" self._flags = flags - return NotImplemented + self._groups = [] def _hasFlag(self, flag): "Check if a flag is set." @@ -40,72 +42,57 @@ def _hasFlag(self, flag): # return False if self._flags & flag == 0 else True def convert(self): - - return NotImplemented + raise NotImplementedError class ArabicNumberConverter(NumberConverter): - def _validate(self): - "Validate that input is a natural Arabic number." - - isinstance(self._arabic, int, "Non-zero integer required, got {0}") - - if self._arabic <= 0: - raise ValueError("Natural number integer required") - return self - def __init__(self, value, flags=0): - super().__init__(value, flags) - self._alphabetic = "" + super().__init__(flags) self._arabic = value - self._groups = [] - self._validate() def _get(self): "Return the alphabetic number representation." return self._alphabetic + def _validate(self): + "Validate that input is a natural Arabic number." -class AlphabeticNumberConverter(NumberConverter): - - _regex = NotImplemented - - def _prepare(self): - "Prepare the alphabetic number for conversion." - - if self._alphabetic: - self._alphabetic = str.lower(str.strip(self._alphabetic)) - return self - else: - raise ValueError("Non-empty string required") + isinstance(self._arabic, int, "Integer required, got {0}") - def _validate(self, regex=""): - "Validate that input is a alphabetic number in appropriate writing system." + if self._arabic <= 0: + raise ValueError("Natural number required") - isinstance(self._alphabetic, str, "Non-empty string required, got {0}") + return self - if re.fullmatch(regex, self._alphabetic): - return NotImplemented - else: - raise ValueError( - "String does not match any pattern for Cyrillic numeral system number" - ) +class AlphabeticNumberConverter(NumberConverter): def __init__(self, alphabetic, flags=0): - super().__init__(alphabetic, flags) + super().__init__(flags) self._alphabetic = alphabetic - self._arabic = 0 - self._groups = [] - self._prepare()._validate() def _get(self): "Return the Arabic number representation." return self._arabic + def _validate(self): + "Validate that input is a alphabetic number in appropriate writing system." + + isinstance(self._alphabetic, str, "String required, got {0}") + + if not self._alphabetic: + raise ValueError("Non-empty string required") + + return self + + def _prepare(self): + "Prepare the alphabetic number for conversion." + + self._alphabetic = str.lower(str.strip(self._alphabetic)) + @classmethod def _translate(cls, alphabetic): From 4c6a709efcbc99fbbf194c40116d8bee760e16cb Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:43:43 +0300 Subject: [PATCH 18/39] _translate() doesn't belong in omninumeric --- omninumeric/greek/greek.py | 9 +++++++++ omninumeric/omninumeric.py | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index e8b6e82..36c2e6f 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -133,6 +133,15 @@ def _calculateMultiplier(cls, index, input): # Use thousand marks if present, otherwise use group index return multiplier + @classmethod + def _translate(cls, alphabetic): + + total = 0 # Current group total value + for k in alphabetic: + total += cls._dict.get(k) + + return total + def _translateGroups(self): "Translate the alphabetic number per group." diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 9f78f41..54bd4b2 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -92,12 +92,3 @@ def _prepare(self): "Prepare the alphabetic number for conversion." self._alphabetic = str.lower(str.strip(self._alphabetic)) - - @classmethod - def _translate(cls, alphabetic): - - total = 0 # Current group total value - for k in alphabetic: - total += cls._dict.get(k) - - return total From 0e9e2a0aec666462ad98cfab4f163588bc5bd544 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:44:12 +0300 Subject: [PATCH 19/39] Fixed _prepare() in omninumeric --- omninumeric/omninumeric.py | 1 + 1 file changed, 1 insertion(+) diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 54bd4b2..2c73b9d 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -92,3 +92,4 @@ def _prepare(self): "Prepare the alphabetic number for conversion." self._alphabetic = str.lower(str.strip(self._alphabetic)) + return self From 4c928f230aba0e5845d3111cb3fa4ff90c545c57 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Fri, 19 Aug 2022 13:25:28 +0300 Subject: [PATCH 20/39] _purgeEmptyGroups() belongs to omninumeric --- omninumeric/cyrillic/cyrillic.py | 9 ++++++++- omninumeric/greek/greek.py | 12 ------------ omninumeric/greek/old/old.py | 2 +- omninumeric/omninumeric.py | 10 ++++++++++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index c0a2ea9..019c9fc 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -186,7 +186,14 @@ def convert(self): Requires a non-empty string. """ - return self._prepare()._validate()._breakIntoGroups()._translateGroups()._get() + return ( + self._prepare() + ._validate() + ._breakIntoGroups() + ._purgeEmptyGroups() + ._translateGroups() + ._get() + ) def to_cu(integer, flags=0): diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 36c2e6f..7efb9cf 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -43,16 +43,6 @@ def _build(self): self._alphabetic = "{0}{1}".format(k, self._alphabetic) return self - def _purgeEmptyGroups(self): - "Remove empty groups from digit group collection." - - for i, k in enumerate(self._groups): - - if not k: - self._groups.pop(i) - - return self - @classmethod def _appendThousandMarksDelim(cls, input, index): "Append thousand marks in delimeter style." @@ -157,8 +147,6 @@ def _breakIntoGroups(self, regex=""): "Break the Cyrillic number in groups of 1-3 digits." self._groups = re.split(regex, self._alphabetic) # Break into groups - for i, k in enumerate(self._groups): - self._groups.pop(i) if not k else True # Purge empty groups self._groups.reverse() # Reverse groups (to ascending order) return self diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index 3ac8295..9a7f114 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -75,4 +75,4 @@ def convert(self): Requires a non-empty string. """ - return self._breakIntoGroups()._translateGroups()._get() + return self._breakIntoGroups()._purgeEmptyGroups()._translateGroups()._get() diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 2c73b9d..298c1df 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -41,6 +41,16 @@ def _hasFlag(self, flag): return self._flags & flag # return False if self._flags & flag == 0 else True + def _purgeEmptyGroups(self): + "Remove empty groups from digit group collection." + + for i, k in enumerate(self._groups): + + if not k: + self._groups.pop(i) # Purge empty groups + + return self + def convert(self): raise NotImplementedError From 61230825c6e8d6cd4fd67297263721c4fba06b62 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 01:46:59 +0300 Subject: [PATCH 21/39] Refactor _appendThousandMarks() --- omninumeric/greek/greek.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 7efb9cf..2bd6e64 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -43,36 +43,24 @@ def _build(self): self._alphabetic = "{0}{1}".format(k, self._alphabetic) return self - @classmethod - def _appendThousandMarksDelim(cls, input, index): - "Append thousand marks in delimeter style." - - if input: - return "{0}{1}".format(cls._dict.get("THOUSAND") * index, input) - else: - return "" - - @classmethod - def _appendThousandMarksPlain(cls, input, index): - "Append thousand marks in plain style." - - result = "" - - for i in input: - result = "{0}{1}".format(result, cls._appendThousandMarksDelim(i, index)) - - return result - def _appendThousandMarks(self, cond): "Append thousand marks according to chosen style (plain or delimeter)." - method = ( - self._appendThousandMarksDelim if cond else self._appendThousandMarksPlain - ) - for i, k in enumerate(self._groups): - self._groups[i] = method(self._groups[i], i) + if k: + if cond: + result = "{0}{1}".format(self._dict.get("THOUSAND") * i, k) + + else: + result = "" + + for l in k: + result = "{0}{1}{2}".format( + result, self._dict.get("THOUSAND") * i, l + ) + + self._groups[i] = result return self From e3b59b92137fdde6ef33cf12a37b366c90b4e244 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:41:58 +0300 Subject: [PATCH 22/39] Rename _getDigit() as _getNumeral(), move to omninumeric --- omninumeric/greek/greek.py | 3 +-- omninumeric/omninumeric.py | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 2bd6e64..70306ff 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -66,7 +66,6 @@ def _appendThousandMarks(self, cond): @classmethod def _getDigit(cls, input): - "Get CU digit for given Arabic digit." return cls._dict.get(input) if input else "" @@ -79,7 +78,7 @@ def _translateGroups(self): index = 0 while k > 0: - result = self._getDigit(k % 10 * pow(10, index)) + result + result = self._getNumeral(k % 10 * pow(10, index)) + result index = index + 1 k = k // 10 diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 298c1df..e93d9ba 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -22,7 +22,7 @@ def get(cls, input): try: return cls(input).name except: - return "" + return None class NumberConverter: @@ -41,6 +41,12 @@ def _hasFlag(self, flag): return self._flags & flag # return False if self._flags & flag == 0 else True + @classmethod + def _getNumeral(cls, numeral, fallback): + "Get a numeral or its value from dictionary." + + return cls._dict.get(numeral) or fallback + def _purgeEmptyGroups(self): "Remove empty groups from digit group collection." @@ -76,6 +82,12 @@ def _validate(self): return self + @classmethod + def _getNumeral(cls, numeral): + "Get alphabetical digit for given Arabic digit." + + return super()._getNumeral(numeral, "") + class AlphabeticNumberConverter(NumberConverter): def __init__(self, alphabetic, flags=0): @@ -103,3 +115,9 @@ def _prepare(self): self._alphabetic = str.lower(str.strip(self._alphabetic)) return self + + @classmethod + def _getNumeral(cls, numeral): + "Get alphabetical digit for given Arabic digit." + + return super()._getNumeral(numeral, 0) From 235a0bed9a2d32159d41b4f61987e944df06a5fb Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:37:33 +0300 Subject: [PATCH 23/39] Merge _translate() with _translateGroups() in greek --- omninumeric/greek/greek.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 70306ff..293dc7c 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -110,23 +110,18 @@ def _calculateMultiplier(cls, index, input): # Use thousand marks if present, otherwise use group index return multiplier - @classmethod - def _translate(cls, alphabetic): - - total = 0 # Current group total value - for k in alphabetic: - total += cls._dict.get(k) - - return total - def _translateGroups(self): "Translate the alphabetic number per group." 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 - self._arabic += self._translate(k) * multiplier + + for l in k: + total += self._getNumeral(l) + + self._arabic += total * multiplier return self From ac2e55c99590de69f3d452bced3091e8025d768c Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:38:56 +0300 Subject: [PATCH 24/39] Add _build() in greek --- omninumeric/cyrillic/cyrillic.py | 1 + omninumeric/greek/greek.py | 9 ++++++++- omninumeric/greek/old/old.py | 8 +++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 019c9fc..48ef80a 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -192,6 +192,7 @@ def convert(self): ._breakIntoGroups() ._purgeEmptyGroups() ._translateGroups() + ._build() ._get() ) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 293dc7c..69b7249 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -97,6 +97,13 @@ def _breakIntoGroups(self): class AlphabeticNumberConverterGreek(AlphabeticNumberConverter): + def _build(self): + "Build the alphabetical number from digit groups." + + for k in self._groups: + self._arabic += k + return self + @classmethod def _calculateMultiplier(cls, index, input): "Calculate multiplier for adjusting digit group value to its registry." @@ -121,7 +128,7 @@ def _translateGroups(self): for l in k: total += self._getNumeral(l) - self._arabic += total * multiplier + self._groups[i] = total * multiplier return self diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index 9a7f114..a5751ee 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -75,4 +75,10 @@ def convert(self): Requires a non-empty string. """ - return self._breakIntoGroups()._purgeEmptyGroups()._translateGroups()._get() + return ( + self._breakIntoGroups() + ._purgeEmptyGroups() + ._translateGroups() + ._build() + ._get() + ) From 67a30fd3830231a878cbee86c275d526a0904d9c Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 23:29:04 +0300 Subject: [PATCH 25/39] Rename ABCs Rename all Arabic* classes to Int* Rename all Alphabetic* classes to Str* --- omninumeric/__init__.py | 6 +++--- omninumeric/cyrillic/cyrillic.py | 4 ++-- omninumeric/greek/__init__.py | 8 ++++---- omninumeric/greek/greek.py | 8 ++++---- omninumeric/greek/old/old.py | 8 ++++---- omninumeric/omninumeric.py | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/omninumeric/__init__.py b/omninumeric/__init__.py index bc57f39..00b16dd 100644 --- a/omninumeric/__init__.py +++ b/omninumeric/__init__.py @@ -1,7 +1,7 @@ from .omninumeric import ( Dictionary, - ArabicNumberConverter, - AlphabeticNumberConverter, + IntNumberConverter, + StrNumberConverter, ) -__all__ = ["Dictionary", "ArabicNumberConverter", "AlphabeticNumberConverter"] +__all__ = ["Dictionary", "IntNumberConverter", "StrNumberConverter"] diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 48ef80a..fb490fa 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -52,7 +52,7 @@ class _CyrillicDictionary(DictionaryGreek): DOT = "." # Dot decorator -class ArabicNumber(ArabicNumberConverterGreek): +class ArabicNumber(IntNumberConverterGreek): _dict = _CyrillicDictionary def _ambiguityCheck(self, cond, flag): @@ -142,7 +142,7 @@ def convert(self): ) -class CyrillicNumber(AlphabeticNumberConverterGreek): +class CyrillicNumber(StrNumberConverterGreek): _dict = _CyrillicDictionary diff --git a/omninumeric/greek/__init__.py b/omninumeric/greek/__init__.py index 71ac4f4..57c6f1b 100644 --- a/omninumeric/greek/__init__.py +++ b/omninumeric/greek/__init__.py @@ -2,14 +2,14 @@ PLAIN, DELIM, DictionaryGreek, - ArabicNumberConverterGreek, - AlphabeticNumberConverterGreek, + IntNumberConverterGreek, + StrNumberConverterGreek, ) __all__ = [ "PLAIN", "DELIM", "DictionaryGreek", - "ArabicNumberConverterGreek", - "AlphabeticNumberConverterGreek", + "IntNumberConverterGreek", + "StrNumberConverterGreek", ] diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 69b7249..7deda3a 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -5,8 +5,8 @@ from omninumeric import ( Dictionary, - ArabicNumberConverter, - AlphabeticNumberConverter, + IntNumberConverter, + StrNumberConverter, ) @@ -35,7 +35,7 @@ def hundreds(cls, start=1, end=10): return cls._getmany(start, end, 100) -class ArabicNumberConverterGreek(ArabicNumberConverter): +class IntNumberConverterGreek(IntNumberConverter): def _build(self): "Build the CU number from digit groups." @@ -96,7 +96,7 @@ def _breakIntoGroups(self): return self -class AlphabeticNumberConverterGreek(AlphabeticNumberConverter): +class StrNumberConverterGreek(StrNumberConverter): def _build(self): "Build the alphabetical number from digit groups." diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index a5751ee..0c97e07 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -3,8 +3,8 @@ from omninumeric import ( - ArabicNumberConverter, - AlphabeticNumberConverter, + StrNumberConverter, + IntNumberConverter, ) from omninumeric.greek import * @@ -44,7 +44,7 @@ class _OldGreekDictionary(DictionaryGreek): DOT = "." # Dot decorator -class ArabicNumber(ArabicNumberConverterGreek): +class ArabicNumber(IntNumberConverterGreek): _dict = _OldGreekDictionary @@ -64,7 +64,7 @@ def convert(self): ) -class OldGreekNumber(AlphabeticNumberConverterGreek): +class OldGreekNumber(StrNumberConverterGreek): _dict = _OldGreekDictionary diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index e93d9ba..53b3e67 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -61,7 +61,7 @@ def convert(self): raise NotImplementedError -class ArabicNumberConverter(NumberConverter): +class IntNumberConverter(NumberConverter): def __init__(self, value, flags=0): super().__init__(flags) @@ -89,7 +89,7 @@ def _getNumeral(cls, numeral): return super()._getNumeral(numeral, "") -class AlphabeticNumberConverter(NumberConverter): +class StrNumberConverter(NumberConverter): def __init__(self, alphabetic, flags=0): super().__init__(flags) From 2ec62f22441e685a261e2b756cca7e20a763a841 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 23:25:22 +0300 Subject: [PATCH 26/39] Rename _arabic and _alphabetic props --- omninumeric/cyrillic/cyrillic.py | 16 ++++++++-------- omninumeric/greek/greek.py | 14 +++++++------- omninumeric/omninumeric.py | 24 +++++++++++++----------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index fb490fa..5cb37fe 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -89,12 +89,12 @@ def _appendTitlo(self, cond): self._dict.get("THOUSAND"), self._dict.get("DOT") ), "\g<1>{0}\g<2>".format(self._dict.get("TITLO")), - self._alphabetic, + self._target, ) - self._alphabetic = ( + self._target = ( result[0] if result[1] > 0 - else "{0}{1}".format(self._alphabetic, self._dict.get("TITLO")) + else "{0}{1}".format(self._target, self._dict.get("TITLO")) ) return self @@ -111,9 +111,9 @@ def _delimDots(self, cond): def _wrapDot(self, cond_a, cond_b): "Prepend and/or append dots if appropriate flags are set." - self._alphabetic = "{0}{1}{2}".format( + self._target = "{0}{1}{2}".format( self._dict.get("DOT") if cond_a else "", - self._alphabetic, + self._target, self._dict.get("DOT") if cond_b else "", ) @@ -158,10 +158,10 @@ def _prepare(self): "Prepare the Cyrillic number for conversion." super()._prepare() - self._alphabetic = re.sub( + self._source = re.sub( "[{0}\{1}]".format(self._dict.get("TITLO"), self._dict.get("DOT")), "", - self._alphabetic, + self._source, ) # Strip ҃decorators return self @@ -169,7 +169,7 @@ def _prepare(self): def _validate(self): super()._validate() - if not re.fullmatch("{0}+".format(self._regex), self._alphabetic): + if not re.fullmatch("{0}+".format(self._regex), self._source): raise ValueError( "String does not match any pattern for Cyrillic numeral system number" ) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 7deda3a..d47dff6 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -40,7 +40,7 @@ def _build(self): "Build the CU number from digit groups." for k in self._groups: - self._alphabetic = "{0}{1}".format(k, self._alphabetic) + self._target = "{0}{1}".format(k, self._target) return self def _appendThousandMarks(self, cond): @@ -89,19 +89,19 @@ def _translateGroups(self): def _breakIntoGroups(self): "Break the Arabic number into groups of 3 digits." - while self._arabic > 0: - self._groups.append(self._arabic % 1000) - self._arabic = self._arabic // 1000 + while self._source > 0: + self._groups.append(self._source % 1000) + self._source = self._source // 1000 return self class StrNumberConverterGreek(StrNumberConverter): def _build(self): - "Build the alphabetical number from digit groups." + "Build the arabic number from digit groups." for k in self._groups: - self._arabic += k + self._target += k return self @classmethod @@ -135,7 +135,7 @@ def _translateGroups(self): def _breakIntoGroups(self, regex=""): "Break the Cyrillic number in groups of 1-3 digits." - self._groups = re.split(regex, self._alphabetic) # Break into groups + self._groups = re.split(regex, self._source) # Break into groups self._groups.reverse() # Reverse groups (to ascending order) return self diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 53b3e67..df89912 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -30,8 +30,8 @@ class NumberConverter: def __init__(self, flags=0): - self._arabic = 0 - self._alphabetic = "" + self._source = 0 + self._target = "" self._flags = flags self._groups = [] @@ -65,19 +65,20 @@ class IntNumberConverter(NumberConverter): def __init__(self, value, flags=0): super().__init__(flags) - self._arabic = value + self._source = value + self._target = "" def _get(self): "Return the alphabetic number representation." - return self._alphabetic + return self._target def _validate(self): "Validate that input is a natural Arabic number." - isinstance(self._arabic, int, "Integer required, got {0}") + isinstance(self._source, int, "Integer required, got {0}") - if self._arabic <= 0: + if self._source <= 0: raise ValueError("Natural number required") return self @@ -93,19 +94,20 @@ class StrNumberConverter(NumberConverter): def __init__(self, alphabetic, flags=0): super().__init__(flags) - self._alphabetic = alphabetic + self._source = alphabetic + self._target = 0 def _get(self): "Return the Arabic number representation." - return self._arabic + return self._target def _validate(self): "Validate that input is a alphabetic number in appropriate writing system." - isinstance(self._alphabetic, str, "String required, got {0}") + isinstance(self._source, str, "String required, got {0}") - if not self._alphabetic: + if not self._source: raise ValueError("Non-empty string required") return self @@ -113,7 +115,7 @@ def _validate(self): def _prepare(self): "Prepare the alphabetic number for conversion." - self._alphabetic = str.lower(str.strip(self._alphabetic)) + self._source = str.lower(str.strip(self._source)) return self @classmethod From bf4abf0eb4b046d12d022b3bbc5adb4da30290e4 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 23:23:14 +0300 Subject: [PATCH 27/39] Move _build() to omninumeric --- omninumeric/greek/greek.py | 14 -------------- omninumeric/omninumeric.py | 7 +++++++ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index d47dff6..57e535c 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -36,13 +36,6 @@ def hundreds(cls, start=1, end=10): class IntNumberConverterGreek(IntNumberConverter): - def _build(self): - "Build the CU number from digit groups." - - for k in self._groups: - self._target = "{0}{1}".format(k, self._target) - return self - def _appendThousandMarks(self, cond): "Append thousand marks according to chosen style (plain or delimeter)." @@ -97,13 +90,6 @@ def _breakIntoGroups(self): class StrNumberConverterGreek(StrNumberConverter): - def _build(self): - "Build the arabic number from digit groups." - - for k in self._groups: - self._target += k - return self - @classmethod def _calculateMultiplier(cls, index, input): "Calculate multiplier for adjusting digit group value to its registry." diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index df89912..3ba2b35 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -41,6 +41,13 @@ def _hasFlag(self, flag): return self._flags & flag # return False if self._flags & flag == 0 else True + def _build(self): + "Build target number from digit groups." + + for k in self._groups: + self._target = k + self._target + return self + @classmethod def _getNumeral(cls, numeral, fallback): "Get a numeral or its value from dictionary." From 9f6d1f8ad51c8e52de0d4e0550150e64a7282634 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 23:26:11 +0300 Subject: [PATCH 28/39] Move _get() to omninumeric --- omninumeric/omninumeric.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 3ba2b35..402e8b4 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -41,6 +41,11 @@ def _hasFlag(self, flag): return self._flags & flag # return False if self._flags & flag == 0 else True + def _get(self): + "Return the converted number." + + return self._target + def _build(self): "Build target number from digit groups." From 9303f66ae494879b0b46320c4cc49f45fab0ca03 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 23:26:44 +0300 Subject: [PATCH 29/39] Improve __init__() logic --- omninumeric/omninumeric.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 402e8b4..6dd3e53 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -28,10 +28,9 @@ def get(cls, input): class NumberConverter: _dict = NotImplemented - def __init__(self, flags=0): - - self._source = 0 - self._target = "" + def __init__(self, source, target, flags=0): + self._source = source + self._target = target self._flags = flags self._groups = [] @@ -75,15 +74,7 @@ def convert(self): class IntNumberConverter(NumberConverter): def __init__(self, value, flags=0): - - super().__init__(flags) - self._source = value - self._target = "" - - def _get(self): - "Return the alphabetic number representation." - - return self._target + super().__init__(value, "", flags) def _validate(self): "Validate that input is a natural Arabic number." @@ -104,15 +95,7 @@ def _getNumeral(cls, numeral): class StrNumberConverter(NumberConverter): def __init__(self, alphabetic, flags=0): - - super().__init__(flags) - self._source = alphabetic - self._target = 0 - - def _get(self): - "Return the Arabic number representation." - - return self._target + super().__init__(alphabetic, 0, flags) def _validate(self): "Validate that input is a alphabetic number in appropriate writing system." From 7a16e090716115844f31847ec3c88ba1d53cdbf9 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:28:02 +0300 Subject: [PATCH 30/39] Rename isinstance() as isinstanceEx() --- omninumeric/omninumeric.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 6dd3e53..597b4bf 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -5,7 +5,7 @@ from enum import Enum, unique -def isinstance(value, cond, msg): +def isinstanceEx(value, cond, msg=""): t = type(value) if not t == cond: @@ -79,7 +79,7 @@ def __init__(self, value, flags=0): def _validate(self): "Validate that input is a natural Arabic number." - isinstance(self._source, int, "Integer required, got {0}") + isinstanceEx(self._source, int, "Integer required, got {0}") if self._source <= 0: raise ValueError("Natural number required") @@ -100,7 +100,7 @@ def __init__(self, alphabetic, flags=0): def _validate(self): "Validate that input is a alphabetic number in appropriate writing system." - isinstance(self._source, str, "String required, got {0}") + isinstanceEx(self._source, str, "String required, got {0}") if not self._source: raise ValueError("Non-empty string required") From e402a8edcce0934a465373e50e82e82d9b85734c Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:30:19 +0300 Subject: [PATCH 31/39] Rename some arguments --- omninumeric/greek/greek.py | 4 ++-- omninumeric/omninumeric.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 57e535c..22adce9 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -91,11 +91,11 @@ def _breakIntoGroups(self): class StrNumberConverterGreek(StrNumberConverter): @classmethod - def _calculateMultiplier(cls, index, input): + def _calculateMultiplier(cls, index, group): "Calculate multiplier for adjusting digit group value to its registry." multiplier = ( - re.match("({0}*)".format(cls._dict.get("THOUSAND")), input) + re.match("({0}*)".format(cls._dict.get("THOUSAND")), group) .groups()[0] .count(cls._dict.get("THOUSAND")) ) # Count trailing thousand marks in the group diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 597b4bf..d631577 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -15,12 +15,13 @@ def isinstanceEx(value, cond, msg=""): @unique class Dictionary(Enum): @classmethod - def get(cls, input): + def get(cls, numeral): + try: - return cls[input].value + return cls[numeral].value except: try: - return cls(input).name + return cls(numeral).name except: return None From 132b2b4bfa117585ae1fd5714ed3a7071397271f Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:32:30 +0300 Subject: [PATCH 32/39] Remove redundant _breakIntoGroups() modification in cyrillic --- omninumeric/cyrillic/cyrillic.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 5cb37fe..28030d5 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -176,9 +176,6 @@ def _validate(self): return self - def _breakIntoGroups(self): - return super()._breakIntoGroups(self._regex) - def convert(self): """ Convert a Cyrillic number into Arabic numeral system. @@ -189,7 +186,7 @@ def convert(self): return ( self._prepare() ._validate() - ._breakIntoGroups() + ._breakIntoGroups(self._regex) ._purgeEmptyGroups() ._translateGroups() ._build() From 6bb3b2d921aac9d62affe3ceab72d46c8f009970 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:34:57 +0300 Subject: [PATCH 33/39] Improve DictionaryGreek logic --- omninumeric/greek/greek.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 22adce9..c41820e 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -18,20 +18,20 @@ class DictionaryGreek(Dictionary): @classmethod def _getmany(cls, start=1, end=10, step=1): r = "" - for i in range(start * step, end * step, step): + for i in range(start * step, (end + 1) * step, step): r += cls(i).name return r @classmethod - def digits(cls, start=1, end=10): + def digits(cls, start=1, end=9): return cls._getmany(start, end, 1) @classmethod - def tens(cls, start=1, end=10): + def tens(cls, start=1, end=9): return cls._getmany(start, end, 10) @classmethod - def hundreds(cls, start=1, end=10): + def hundreds(cls, start=1, end=9): return cls._getmany(start, end, 100) From 10bfd5218707e6ef74894dae1ec58cbac31475f8 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sat, 20 Aug 2022 22:25:04 +0300 Subject: [PATCH 34/39] Annotations --- omninumeric/cyrillic/cyrillic.py | 47 ++++++++++++----------- omninumeric/greek/greek.py | 64 ++++++++++++++++++++++++++------ omninumeric/greek/old/old.py | 14 +++++-- omninumeric/omninumeric.py | 58 +++++++++++++++++++++++++---- 4 files changed, 139 insertions(+), 44 deletions(-) diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index 28030d5..e9c83b2 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -1,24 +1,25 @@ # -*- coding: UTF-8 -*- # For licensing information see LICENSE file included in the project's root directory. # To learn about Cyrillic numeral system (CU), see INTRODUCTION.md -"Module for number conversion between Arabic and Cyrillic numeral systems." +"This module provides tools for two-way conversion with Cyrillic numeral system." import re from omninumeric.greek import * -CU_PLAIN = PLAIN -CU_DELIM = DELIM -CU_NOTITLO = 0b10 # DO NOT append titlo -CU_ENDDOT = 0b100 # Append dot -CU_PREDOT = 0b1000 # Prepend dot -CU_DOT = 0b10000 # Delimeter dots (private, for internal flag checks) -CU_DELIMDOT = CU_DOT | CU_DELIM # Delimeter dots (forces delim style) -CU_WRAPDOT = CU_ENDDOT | CU_PREDOT # Wrap in dots -CU_ALLDOT = CU_ENDDOT | CU_PREDOT | CU_DELIMDOT # Wrapper and delimeter dots +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 class _CyrillicDictionary(DictionaryGreek): + "Cyrillic numerals ditcionary." а = 1 в = 2 @@ -53,6 +54,8 @@ class _CyrillicDictionary(DictionaryGreek): class ArabicNumber(IntNumberConverterGreek): + "Number converter into Cyrillic numeral system." + _dict = _CyrillicDictionary def _ambiguityCheck(self, cond, flag): @@ -68,7 +71,7 @@ def _ambiguityCheck(self, cond, flag): return self def _swapDigits(self): - "Swap digits in 11-19." + "Swap digits for values 11-19 (unless separated)." for i, k in enumerate(self._groups): @@ -81,7 +84,7 @@ def _swapDigits(self): return self def _appendTitlo(self, cond): - "Append titlo unless appropriate flag is set." + 'Apply "titlo" decorator unless appropriate flag is set.' if not cond: result = re.subn( @@ -100,7 +103,7 @@ def _appendTitlo(self, cond): return self def _delimDots(self, cond): - "Insert dots between digit groups if appropriate flag is set." + "Insert dots between numeral groups if appropriate flag is set." if cond: for i, k in enumerate(self._groups[1:]): @@ -109,7 +112,7 @@ def _delimDots(self, cond): return self def _wrapDot(self, cond_a, cond_b): - "Prepend and/or append dots if appropriate flags are set." + "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 "", @@ -121,7 +124,7 @@ def _wrapDot(self, cond_a, cond_b): def convert(self): """ - Convert an Arabic number into Cyrillic numeral system. Uses plain style by default. + Convert into Cyrillic numeral system. Uses plain style by default. Requires a non-zero integer. """ @@ -143,6 +146,7 @@ def convert(self): class CyrillicNumber(StrNumberConverterGreek): + "Number converter from Cyrillic numeral system." _dict = _CyrillicDictionary @@ -152,10 +156,10 @@ class CyrillicNumber(StrNumberConverterGreek): _dict.tens(2), _dict.digits(), _dict.get(10), - ) + ) # Regular expression for typical Cyrillic numeral system number def _prepare(self): - "Prepare the Cyrillic number for conversion." + "Prepare source number for conversion." super()._prepare() self._source = re.sub( @@ -167,18 +171,19 @@ def _prepare(self): return 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): raise ValueError( - "String does not match any pattern for Cyrillic numeral system number" + "String does not match any pattern for Cyrillic numeral system numbers" ) return self def convert(self): """ - Convert a Cyrillic number into Arabic numeral system. + Convert from Cyrillic numeral system. Requires a non-empty string. """ @@ -195,12 +200,12 @@ def convert(self): def to_cu(integer, flags=0): - "Deprecated; use ArabicNumber().convert() instead." + "Deprecated. Use ArabicNumber().convert() instead." return ArabicNumber(integer, flags).convert() def to_arab(alphabetic, flags=0): - "Deprecated; use CyrillicNumber().convert() instead." + "Deprecated. Use CyrillicNumber().convert() instead." return CyrillicNumber(alphabetic).convert() diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index c41820e..c1a8bc5 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -1,5 +1,6 @@ # -*- coding: UTF-8 -*- # For licensing information see LICENSE file included in the project's root directory. +"This module provides basic tools for two-way conversion with Greek-type alphabetic numeral systems." import re @@ -10,13 +11,27 @@ ) -PLAIN = 0 # Write in plain style -DELIM = 0b1 # Read/write in delim style +PLAIN = 0 # Write in plain style flag +DELIM = 0b1 # Read/write in delim style flag class DictionaryGreek(Dictionary): + """ + ABC for Greek-type alphabetic numeral systems ditcionaries. + + Derive from this class to define numeral dictionaries for Greek type alphabetic numeral systems. + """ + @classmethod def _getmany(cls, start=1, end=10, step=1): + """ + Look a range of numerals up in dictionary. + + @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) + step - numeral value increment (i.e. 1 for range of 1, 2, 3...; 10 for range of 10, 20, 30...) + """ + r = "" for i in range(start * step, (end + 1) * step, step): r += cls(i).name @@ -24,18 +39,42 @@ def _getmany(cls, start=1, end=10, step=1): @classmethod def digits(cls, start=1, end=9): + """ + Get a range of numerals in digits registry. + + @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) @classmethod def tens(cls, start=1, end=9): + """ + Get a range of numerals in tens registry. + + @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) @classmethod def hundreds(cls, start=1, end=9): + """ + Get a range of numerals in hundreds registry. + + @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) class IntNumberConverterGreek(IntNumberConverter): + """ + 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): "Append thousand marks according to chosen style (plain or delimeter)." @@ -57,13 +96,8 @@ def _appendThousandMarks(self, cond): return self - @classmethod - def _getDigit(cls, input): - - return cls._dict.get(input) if input else "" - def _translateGroups(self): - "Translate the Arabic number per group." + "Translate groups of numerals one by one." for i, k in enumerate(self._groups): @@ -80,7 +114,7 @@ def _translateGroups(self): return self def _breakIntoGroups(self): - "Break the Arabic number into groups of 3 digits." + "Break source number into groups of 3 numerals." while self._source > 0: self._groups.append(self._source % 1000) @@ -90,9 +124,15 @@ def _breakIntoGroups(self): class StrNumberConverterGreek(StrNumberConverter): + """ + ABC for number conversion from Greek-type alphabetic numeral systems. + + Derive from this class to define converters from Greek-type alphabetic numeral systems. + """ + @classmethod def _calculateMultiplier(cls, index, group): - "Calculate multiplier for adjusting digit group value to its registry." + '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) @@ -104,7 +144,7 @@ def _calculateMultiplier(cls, index, group): return multiplier def _translateGroups(self): - "Translate the alphabetic number per group." + "Translate groups of numerals one by one." for i, k in enumerate(self._groups): total = 0 # Current group total value @@ -119,7 +159,7 @@ def _translateGroups(self): return self def _breakIntoGroups(self, regex=""): - "Break the Cyrillic number in groups of 1-3 digits." + "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) diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index 0c97e07..ba92f03 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -1,5 +1,10 @@ # -*- coding: UTF-8 -*- # For licensing information see LICENSE file included in the project's root directory. +""" +This module provides tools for two-way conversion with Old Greek numeral system. + +WIP +""" from omninumeric import ( @@ -10,6 +15,7 @@ class _OldGreekDictionary(DictionaryGreek): + "Old Greek numerals dictionary" α = 1 β = 2 @@ -38,19 +44,20 @@ class _OldGreekDictionary(DictionaryGreek): ψ = 700 ω = 800 ϡ = 900 - THOUSAND = "͵" # Thousand mark + THOUSAND = "͵" # "Thousand" mark KERAIA = "ʹ" # "Keraia" decorator OVERLINE = "̅" # Overline decorator DOT = "." # Dot decorator class ArabicNumber(IntNumberConverterGreek): + "Number converter into Old Greek numeral system." _dict = _OldGreekDictionary def convert(self): """ - Convert an Arabic number into Cyrillic numeral system. Uses plain style by default. + Convert into Old Greek numeral system. Uses plain style by default. Requires a non-zero integer. """ @@ -65,12 +72,13 @@ def convert(self): class OldGreekNumber(StrNumberConverterGreek): + "Number converter from Old Greek numeral system." _dict = _OldGreekDictionary def convert(self): """ - Convert a Cyrillic number into Arabic numeral system. + Convert from Old Greek numeral system. Requires a non-empty string. """ diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index d631577..d8dae13 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -1,11 +1,19 @@ # -*- coding: UTF-8 -*- # For licensing information see LICENSE file included in the project's root directory. +"This module provides basic tools for two-way conversion with alphabetic numeral systems." import re from enum import Enum, unique def isinstanceEx(value, cond, msg=""): + """ + Test if value is of a speciic type, raise error with specified message if not. + + @value - value to test + @cond - type to test against + @msg - error message to print. Supports format() to print @value type + """ t = type(value) if not t == cond: @@ -14,8 +22,19 @@ def isinstanceEx(value, cond, msg=""): @unique class Dictionary(Enum): + """ + ABC for alphabetic numeral systems dictionaries. + + Derive from this class to define numeral dictionaries for alphabetic numeral systems. + """ + @classmethod def get(cls, numeral): + """ + Look a numeral up in dictionary. + + @numeral - str or int to look up. Returns int if str is found and vice versa, returns None if nothing found + """ try: return cls[numeral].value @@ -27,6 +46,12 @@ def get(cls, numeral): class NumberConverter: + """ + ABC for number conversion. + + Derive from this class to define converters into and from alphabetic numeral systems. + """ + _dict = NotImplemented def __init__(self, source, target, flags=0): @@ -47,7 +72,7 @@ def _get(self): return self._target def _build(self): - "Build target number from digit groups." + "Build the converted number from groups of numerals." for k in self._groups: self._target = k + self._target @@ -55,12 +80,17 @@ def _build(self): @classmethod def _getNumeral(cls, numeral, fallback): - "Get a numeral or its value from dictionary." + """ + Look a numeral up in dictionary. + + @numeral - numeral to look up + @fallback - value to return if @numeral is not found + """ return cls._dict.get(numeral) or fallback def _purgeEmptyGroups(self): - "Remove empty groups from digit group collection." + "Remove empty groups from numeral groups collection." for i, k in enumerate(self._groups): @@ -74,11 +104,17 @@ def convert(self): class IntNumberConverter(NumberConverter): + """ + ABC for number conversion into alphabetic numeral systems. + + Derive from this class to define converters into alphabetic numeral systems. + """ + def __init__(self, value, flags=0): super().__init__(value, "", flags) def _validate(self): - "Validate that input is a natural Arabic number." + "Validate that source number is a natural number." isinstanceEx(self._source, int, "Integer required, got {0}") @@ -89,17 +125,23 @@ def _validate(self): @classmethod def _getNumeral(cls, numeral): - "Get alphabetical digit for given Arabic digit." + "Get alphabetic digit for given value." return super()._getNumeral(numeral, "") class StrNumberConverter(NumberConverter): + """ + ABC for number conversion from alphabetic numeral systems. + + Derive from this class to define converters from ABS. + """ + def __init__(self, alphabetic, flags=0): super().__init__(alphabetic, 0, flags) def _validate(self): - "Validate that input is a alphabetic number in appropriate writing system." + "Validate that source number is a non-empty string." isinstanceEx(self._source, str, "String required, got {0}") @@ -109,13 +151,13 @@ def _validate(self): return self def _prepare(self): - "Prepare the alphabetic number for conversion." + "Prepare source number for further operations." self._source = str.lower(str.strip(self._source)) return self @classmethod def _getNumeral(cls, numeral): - "Get alphabetical digit for given Arabic digit." + "Get value for given alphabetic digit." return super()._getNumeral(numeral, 0) From 628b54968031fe9c078cea8608cea7143c73b7ff Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sun, 21 Aug 2022 01:44:48 +0300 Subject: [PATCH 35/39] Fixed _purgeEmptyGroups() --- omninumeric/greek/greek.py | 2 +- omninumeric/omninumeric.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index c1a8bc5..2fc2f46 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -139,7 +139,7 @@ def _calculateMultiplier(cls, index, group): .groups()[0] .count(cls._dict.get("THOUSAND")) ) # Count trailing thousand marks in the group - multiplier = pow(1000, multiplier if multiplier else index - 1) + multiplier = pow(1000, multiplier if multiplier else index) # Use thousand marks if present, otherwise use group index return multiplier diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index d8dae13..9a93ebe 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -92,11 +92,10 @@ def _getNumeral(cls, numeral, fallback): def _purgeEmptyGroups(self): "Remove empty groups from numeral groups collection." - for i, k in enumerate(self._groups): - - if not k: - self._groups.pop(i) # Purge empty groups - + print(self._groups) + while self._groups.count(""): + self._groups.remove("") # Purge empty groups + print(self._groups) return self def convert(self): From 22455d160d4e694aa8e3ba6817a495c02d0af6f1 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Thu, 18 Aug 2022 00:41:49 +0300 Subject: [PATCH 36/39] Update doco --- docs/CHANGELOG.md | 29 ----------------------- docs/CHANGELOG.ru.md | 29 ----------------------- docs/README.md | 40 +++++++++++++++++++------------- docs/README.ru.md | 32 +++++++++++++++---------- omninumeric/cyrillic/cyrillic.py | 4 ++-- omninumeric/greek/greek.py | 4 ++-- omninumeric/greek/old/old.py | 4 ++-- omninumeric/omninumeric.py | 4 ++-- 8 files changed, 52 insertions(+), 94 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7b0270a..9d8d7a3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,35 +2,6 @@ 🌏 English [Русский](./CHANGELOG.ru.md) -## 1.3.2 - -- Added: Exceptions on unexpected input - -## 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 -- Added: Obligatory dotting in ambiguous "delimeter" style cases - -## 1.2.0 - -- Added: Now supports reading/writing in "plain" style -- Added: Flags to specify Arabic to CU conversion mode (default to "delimeter" style) -- Added: Flag to omit "titlo" - -## 1.1.1 - -- Cleanup - -## 1.1.0 - -- Added: Now supports Python >= 3.5 -- Fixed: Wrong "titlo" positioning -- Fixed: Wrong "800" digit - ## 1.0.0 diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index 6566786..2703993 100644 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -2,35 +2,6 @@ 🌏 [English](./CHANGELOG.md) Русский -## 1.3.2 - -- Добавлено: Исключения при некорректных входных данных - -## 1.3.1 - -- Оптимизация - -## 1.3.0 - -- Добавлено: Флаги декорирования точками: конечная, с обеих сторон, точки-разделители разрядов, или любая их комбинация -- Добавлено: Обязательное проставление точек-разделителей в неоднозначных случаях - -## 1.2.0 - -- Добавлено: Поддержка чтения/записи в "сплошном" записи -- Добавлено: Флаг выбора варианта записи при преобразовании в ЦСЯ ("по группам" по умолчанию) -- Добавлено: Флаг для опускания вывода "титла" - -## 1.1.1 - -- Полировка - -## 1.1.0 - -- Добавлено: Поддержка Python >= 3.5 -- Исправлено: Неверная позиция "титла" -- Исправлено: Неверная цифра "800" - ## 1.0.0 diff --git a/docs/README.md b/docs/README.md index 99c1bfc..60a1d80 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,46 +1,54 @@ # Omninumeric -![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cu-numbers) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/cu-numbers) [![Codecov](https://img.shields.io/codecov/c/github/endrain/cu-numbers)](https://app.codecov.io/gh/endrain/cu-numbers) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/omninumeric) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/omninumeric) [![Codecov](https://img.shields.io/codecov/c/github/endrain/omninumeric)](https://app.codecov.io/gh/endrain/omninumeric) -[![PyPI - License](https://img.shields.io/pypi/l/cu-numbers)](./LICENSE) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![PyPI - License](https://img.shields.io/pypi/l/omninumeric)](./LICENSE) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 🌏 English [Русский](./README.ru.md) -A program for numbers conversion between Arabic and Cyrillic (*further CU*) numeral systems. +Omninumeric provides support for number reading and writing in alphabetic numeral systems. + +## Supported numeral systems + +- [x] Cyrillic +- [] Roman - WIP +- [] Byzantian Greek - WIP +- [] Modern Greek - planned +- [] Hebrew - planned ## Background -See [Introduction](./INTRODUCTION.md) to learn about CU numeral system. +See [Introduction](./INTRODUCTION.md) to learn about Cyrillic numeral system. ## Installation - pip install cu-numbers + pip install omninumeric ## Usage - import cunumbers.cunumbers as cu + import omninumeric.cyrillic as CU - # Convert an Arabic number to CU + # Convert a number into Cyrillic # Requires non-zero int, returns str - a = cu.to_cu(1) + a = CU.ArabicNumber(1).convert() - # Convert a CU number to Arabic + # Convert a Cyrillic number to Arabic # Requires non-empty str, returns int - b = cu.to_arab("а҃") + b = CU.CyrillicNumber("а҃").convert() -"Delimiter" and "plain" style numbers are supported in both directions. "Delimeter" style is default for CU-wise conversion. +"Delimiter" and "plain" style numbers are supported both for reading and writing, 'plain" style is used by default for writing. -Several falgs can be used with `to_cu()` method: +When writing into Cyrillic, several falgs can be used: - # CU_PLAIN flag sets conversion to "plain" style + # CU_DELIM flag sets conversion to "delimeter" style - c = cu.to_cu(111111, CU_PLAIN) + c = cu.to_alphabetic(111111, CU_DELIM) - # CU_NOTITLO flag omits "titlo" output + # CU_NOTITLO flag omits "titlo" decorator - d = cu.to_cu(11000, CU_PLAIN | CU_NOTITLO) + d = cu.to_alphabetic(11000, CU_DELIM | CU_NOTITLO) # Following flags control dot styling: # diff --git a/docs/README.ru.md b/docs/README.ru.md index eb8bbc0..5e91a67 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -1,12 +1,20 @@ # Omninumeric -![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cu-numbers) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/cu-numbers) [![Codecov](https://img.shields.io/codecov/c/github/endrain/cu-numbers)](https://app.codecov.io/gh/endrain/cu-numbers) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/omninumeric) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/omninumeric) [![Codecov](https://img.shields.io/codecov/c/github/endrain/omninumeric)](https://app.codecov.io/gh/endrain/omninumeric) -[![PyPI - License](https://img.shields.io/pypi/l/cu-numbers)](./LICENSE.ru) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![PyPI - License](https://img.shields.io/pypi/l/omninumeric)](./LICENSE.ru) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 🌏 [English](./README.md) Русский -Программа для преобразования чисел между арабской и церковнославянской системами записи. +Программа для чтения и записи чисел в алфавитных системах записи. + +## Поддерживаемые системы записи + +- [x] Церковно-славянская +- [] Римская - в работе +- [] Византийская греческая - в работе +- [] Современная греческая - в проекте +- [] Еврейская - в проекте ## Историческая справка @@ -14,33 +22,33 @@ ## Установка - pip install cu-numbers + pip install omninumeric ## Использование - import cunumbers.cunumbers as cu + import omninumeric.cyrillic as cu # Преобразовать арабское число в церковнославянское # Принимает ненулевой int, возвращает str - a = cu.to_cu(1) + a = CU.ArabicNumber(1).convert() # Преобразовать церковнославянское число в арабское # Принимает непустой str, возвращает int - b = cu.to_arab("а҃") + b = CU.CyrillicNumber("а҃").convert() -В обоих направлениях поддерживаются варианты записи "сплошной" и "по группам". Запись "по группам" используется по умолчанию для преобразования в ЦСЯ. +В обоих направлениях поддерживаются варианты записи "сплошной" и "по группам", "сплошная" запись используется по умолчанию. -Метод `to_cu()` принимает несколько флагов: +При записи в ЦСЯ возможно использование слеедующих флагов: - # CU_PLAIN устанавливает "сплошной" вариант записи для преобразования в ЦСЯ + # CU_DELIM устанавливает "сплошной" вариант записи в ЦСЯ - c = cu.to_cu(111111, CU_PLAIN) + c = cu.to_alphabetic(111111, CU_DELIM) # CU_NOTITLO опускает вывод знака "титло" - d = cu.to_cu(11000, CU_PLAIN | CU_NOTITLO) + d = cu.to_alphabetic(11000, CU_DELIM | CU_NOTITLO) # Следующие флаги управляют декорированием точками: # diff --git a/omninumeric/cyrillic/cyrillic.py b/omninumeric/cyrillic/cyrillic.py index e9c83b2..7f83149 100644 --- a/omninumeric/cyrillic/cyrillic.py +++ b/omninumeric/cyrillic/cyrillic.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- -# For licensing information see LICENSE file included in the project's root directory. +# For licensing information see LICENSE file included with the project. # To learn about Cyrillic numeral system (CU), see INTRODUCTION.md -"This module provides tools for two-way conversion with Cyrillic numeral system." +"This module provides tools for reading and writing numbers in Cyrillic numeral system." import re from omninumeric.greek import * diff --git a/omninumeric/greek/greek.py b/omninumeric/greek/greek.py index 2fc2f46..d60f09f 100644 --- a/omninumeric/greek/greek.py +++ b/omninumeric/greek/greek.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- -# For licensing information see LICENSE file included in the project's root directory. -"This module provides basic tools for two-way conversion with Greek-type alphabetic numeral systems." +# For licensing information see LICENSE file included with the project. +"This module provides basic tools for reading and writing numbers in Greek-type alphabetic numeral systems." import re diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old/old.py index ba92f03..c52befe 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old/old.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- -# For licensing information see LICENSE file included in the project's root directory. +# For licensing information see LICENSE file included with the project. """ -This module provides tools for two-way conversion with Old Greek numeral system. +This module provides tools for reading and writing numbers in Old Greek numeral system. WIP """ diff --git a/omninumeric/omninumeric.py b/omninumeric/omninumeric.py index 9a93ebe..48ce585 100644 --- a/omninumeric/omninumeric.py +++ b/omninumeric/omninumeric.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- -# For licensing information see LICENSE file included in the project's root directory. -"This module provides basic tools for two-way conversion with alphabetic numeral systems." +# For licensing information see LICENSE file included with the project. +"This module provides basic tools for reading and writing numbers in alphabetic numeral systems." import re from enum import Enum, unique From c85079781228940656520144050b9aedd45aea2e Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sun, 21 Aug 2022 02:37:21 +0300 Subject: [PATCH 37/39] Update doco --- docs/README.md | 10 +++++----- docs/README.ru.md | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/README.md b/docs/README.md index 60a1d80..460ee1a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,10 +11,10 @@ Omninumeric provides support for number reading and writing in alphabetic numera ## Supported numeral systems - [x] Cyrillic -- [] Roman - WIP -- [] Byzantian Greek - WIP -- [] Modern Greek - planned -- [] Hebrew - planned +- [ ] Roman - WIP +- [ ] Byzantian Greek - WIP +- [ ] Modern Greek - planned +- [ ] Hebrew - planned ## Background @@ -38,7 +38,7 @@ See [Introduction](./INTRODUCTION.md) to learn about Cyrillic numeral system. b = CU.CyrillicNumber("а҃").convert() -"Delimiter" and "plain" style numbers are supported both for reading and writing, 'plain" style is used by default for writing. +"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: diff --git a/docs/README.ru.md b/docs/README.ru.md index 5e91a67..de443d0 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -11,10 +11,10 @@ ## Поддерживаемые системы записи - [x] Церковно-славянская -- [] Римская - в работе -- [] Византийская греческая - в работе -- [] Современная греческая - в проекте -- [] Еврейская - в проекте +- [ ] Римская - в работе +- [ ] Византийская греческая - в работе +- [ ] Современная греческая - в проекте +- [ ] Еврейская - в проекте ## Историческая справка From 77e567ab9ed926ae3f8531d99e3aa5e2e1cd03cf Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Sun, 21 Aug 2022 02:25:50 +0300 Subject: [PATCH 38/39] greek.old as module --- omninumeric/greek/{old => }/old.py | 2 ++ omninumeric/greek/old/__init__.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) rename omninumeric/greek/{old => }/old.py (97%) delete mode 100644 omninumeric/greek/old/__init__.py diff --git a/omninumeric/greek/old/old.py b/omninumeric/greek/old.py similarity index 97% rename from omninumeric/greek/old/old.py rename to omninumeric/greek/old.py index c52befe..b14c7ed 100644 --- a/omninumeric/greek/old/old.py +++ b/omninumeric/greek/old.py @@ -6,6 +6,8 @@ WIP """ +__all__ = ["ArabicNumber", "OldGreekNumber"] + from omninumeric import ( StrNumberConverter, diff --git a/omninumeric/greek/old/__init__.py b/omninumeric/greek/old/__init__.py deleted file mode 100644 index d4bd9d0..0000000 --- a/omninumeric/greek/old/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .old import ArabicNumber, OldGreekNumber - -__all__ = ["ArabicNumber", "OldGreekNumber"] From 3ba6312d1239ccd0c086f18df40eb6381d1f4616 Mon Sep 17 00:00:00 2001 From: Andrey Shur Date: Thu, 18 Aug 2022 02:24:31 +0300 Subject: [PATCH 39/39] Update setup --- setup.cfg | 30 ++---------------------------- setup.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 28 deletions(-) create mode 100644 setup.py diff --git a/setup.cfg b/setup.cfg index 6a443a6..f9ab3b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,31 +1,5 @@ [metadata] -name = omninumeric -version = 1.0.0 -author = Andrei Shur -author_email = amshoor@gmail.com -description = Convert numbers between Arabic and alphabetic numeral systems -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/endrain/omninumeric -keywords = church slavonic, conversion -license = MIT -classifiers = - Development Status :: 4 - Beta - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - License :: OSI Approved :: MIT License - Operating System :: OS Independent - Intended Audience :: Religion - Intended Audience :: Science/Research +long_description = file: docs\README.md [options] -include_package_data = True -packages = - omninumeric - cyrillic -python_requires = >=3.4 \ No newline at end of file +include_package_data = True \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ec58b24 --- /dev/null +++ b/setup.py @@ -0,0 +1,37 @@ +from setuptools import setup, find_packages + +setup( + name="omninumeric", + version="1.0.0", + author="Andrei Shur", + author_email="amshoor@gmail.com", + description="Read and write numbers in alphabetic numeral systems", + long_description_content_type="text/markdown", + url="https://github.com/endrain/omninumeric", + keywords=[ + "church slavonic", + "conversion", + ], + license="MIT", + classifiers=[ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Localization", + "Intended Audience :: Developers", + "Intended Audience :: Religion", + "Intended Audience :: Science/Research", + ], + packages=find_packages(), + python_requires=">=3.4", +)