diff --git a/.gitignore b/.gitignore index b6e4761..aa27198 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +pyproject.toml # PyInstaller # Usually these files are written by a python script from a template diff --git a/INTRODUCTION.md b/INTRODUCTION.md new file mode 100644 index 0000000..64f8cfe --- /dev/null +++ b/INTRODUCTION.md @@ -0,0 +1,42 @@ +# ABOUT CHURCH SLAVONIC NUMBERS + +## 1. Numerals + +Church Slavonic script (*further CU*) has individual numerals to represent numbers from 1 to 9, each round ten and each round hundred, for a total of 27 numerals. There's no zero digit. + +## 2. Basic numbers + +A number `x < 1000` in CU is represented by a succession of 1 to 3 CU digits that may contain a hundreds digit, a tens digit and a proper digit in that order. Digits representing numbers 11-19 are swapped places, wheter or not a hundreds digit is present. + +For the purpose of this program, this is to be referenced as a "hundred group". + +**Examples:** +CU | Arabic +---|--- +а҃ | 1 +і҃ | 10 +р҃ | 100 +ра҃і | 111 - note digit swapping +рк҃а | 121 + +## 3. Thousands +A number `x >= 1000` in CU is prepended with a "thousand" sign. For each "thousand" sign before a number, the number has to be mutiplied by one thousand. + +## 4. Number building + +Altogether, a complete number in CU consists of a succession of hundred groups with "thousand" signs inbetween. + +**Examples:** +CU | Arabic +---|--- +а҃ | 1 +҂а҃ | 1000 +҂҂а҃ | 1000000 +҂҂а҂аа҃ | 1001001 + +## 5. Decoration +Finally, there is a "titlo" superscript sign that's obligatory to CU numbers. "Titlo" is placed in the number's rightmost hundred group; above the 2nd-from-last digit if it exists, otherwise above the only digit. + +**Examples:** + +See examples above. **NB:** in some (many) fonts "titlo" appears to follow after a digit, not above it. \ No newline at end of file diff --git a/README.md b/README.md index 92dcc8f..5751acc 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -A program for number convertion between Church Slavonic and arabic. \ No newline at end of file +# cu-numbers + +A program for numbers conversion between Church Slavonic script (*further CU*) and Arabic numerals. + +## Background + +See [Introduction](./INTRODUCTION.md) to learn about CU numbers. + +## Requirements + + Python >= 3.7 + +## Installation + + pip install cu-numbers + +## Usage + + import cunumbers + + # Convert an Arabic number to CU + # Requires non-zero int, returns str + + a = cunumbers.arab_to_cu(1) + + # Convert a CU number to an Arabic + # Requires str, returns int + + b = cunumbers.cu_to_arab("а҃") + +## Contributing + +Create an issue describing a bug or suggestion, then create a pull request mentioning the issue. + +## Feedback + +Drop me a line: amshoor@gmail.com + +## License + +See [LICENSE](./LICENSE). \ No newline at end of file diff --git a/cunumbers/__init__.py b/cunumbers/__init__.py new file mode 100644 index 0000000..aaf4784 --- /dev/null +++ b/cunumbers/__init__.py @@ -0,0 +1 @@ +from .cunumbers import arab_to_cu, cu_to_arab \ No newline at end of file diff --git a/cu-numbers.py b/cunumbers/cunumbers.py similarity index 61% rename from cu-numbers.py rename to cunumbers/cunumbers.py index 4c5c153..5df6b8e 100644 --- a/cu-numbers.py +++ b/cunumbers/cunumbers.py @@ -1,38 +1,7 @@ -# ABOUT CHURCH SLAVONIC NUMBERS -# -# Church Slavonic (CS) script has individual digits to represent numbers from 1 to 9, -# each round ten and each round hundred, for a total of 27 digits. There's no zero digit. -# -# A number x < 1000 in CS script is represented by a succession of 1 to 3 CS digits -# that may contain a hundreds digit, a tens digit and a proper digit in that order. -# Digits representing numbers 11-19 are swapped places, wheter or not a hundreds digit is present. -# -# For the purpose of this program, this is to be referenced as "a hundred group". -# -# Examples: -# а҃ = 1 -# і҃ = 10 -# р҃ = 100 -# ра҃і = 111 - note the digit swapping -# рк҃а = 121 -# -# A number x >= 1000 in CS script is prepended with a "thousand" sign. -# For each "thousand" sign before a number, the number has to be mutiplied by a thousand. -# -# Altogether, a complete number in CS script consists of a succession of hundred groups -# with "thousand" signs inbetween. -# -# Examples: -# а҃ = 1 -# ҂а҃ = 1000 -# ҂҂а҃ = 1000000 -# ҂҂а҂аа҃ = 1001001 -# -# There's also a "titlo" superscript sign that's obligatory to Church Slavonic numbers. -# "Titlo" is placed in the number's rightmost hundred group; -# above the 2nd-from-last digit if it exists, otherwise above the only digit. -# See examples above. - +# For licensing information see LICENSE file included in the project's root directory. +""" +Module for number conversion between Church Slavonic and Arabic. +""" import re @@ -46,22 +15,22 @@ cu_dict = cu_null + cu_digits + cu_null + cu_tens + cu_null + cu_hundreds -# Process an arabic hundred group -def _write_cu_hundred(hundred): +def _write_cu_hundred(hundred = 0): + """Process an arabic hundred group.""" return cu_dict[20 + hundred // 100] + cu_dict[10 + hundred % 100 // 10] + cu_dict[hundred % 10] -# Process an arabic number in hundred groups -def _write_cu_number(index, number, result): +def _write_cu_number(number = 0, index = 0, result = ""): + """Process an arabic number per hundred group.""" # @index arg counts the amount of hundred groups in a number - # to add the appropriate amount of the "҂" between each hundred group. + # to add the appropriate amount of "҂" before each hundred group. # Process leading hundred. Prepend with "҂" times @index if @index > 0 sub_result = cu_thousand * index + _write_cu_hundred(number % 1000) + result if number // 1000: # If the number is still >1000: @index++, drop last 3 digits and repeat - return _write_cu_number(index + 1, number // 1000, sub_result) + return _write_cu_number(number // 1000, index + 1, sub_result) else: # Purge zero-groups and individual zeroes @@ -77,7 +46,8 @@ def _write_cu_number(index, number, result): return sub_result # And we're done -def _read_cu_hundred(index, input): +def _read_cu_hundred(input = "" , index = 0): + """Process a CU hundred group.""" # @index arg holds current position of a hundred group in the number # Swap digits in numbers 11-19 @@ -102,9 +72,10 @@ def _read_cu_hundred(index, input): return subtotal -# Process a Church Slavonic number -def _read_cu_number(input): - sub_result = str.strip(input) +def _read_cu_number(input = ""): + """Process a CU number per hundred group.""" + + sub_result = input # Strip ҃"҃ " sub_result = re.sub("[%s]" % cu_titlo, "", input) @@ -118,6 +89,47 @@ def _read_cu_number(input): result = 0 for i, k in enumerate(hundreds): - result += _read_cu_hundred(i, k[::-1]) + result += _read_cu_hundred(k[::-1], i) + + return(result) + + +def prepare(input): + """Prepare a CU number for conversion.""" + + input = str.lower(str.strip(input)) # Trim and lowercase + if re.fullmatch("([%s]*[%s]{1,4})+" % (cu_thousand, cu_digits + cu_tens + cu_hundreds + cu_titlo), input) == None: + raise ValueError("String does not match the pattern for Church Slavonic script number") + else: + return input + - return(result) \ No newline at end of file + +def arab_to_cu(input): + """ + Convert an Arabic number into Church Slavonic script. + + Requires a non-zero integer. + """ + + t = type(input) + if t != int: + raise TypeError("Non-zero integer required, got %s" % t) + elif input <= 0: + raise ValueError("Non-zero integer required") + else: + return _write_cu_number(input) + + +def cu_to_arab(input: str): + """ + Convert a Church Slavonic script number into Arabic. + + Requires a string. + """ + + t = type(input) + if t != str: + raise TypeError("String required, got %s" % t) + else: + return _read_cu_number(prepare(input)) \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1eb94c9 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[metadata] +name = cu-numbers +version = 1.0.0 +author = Andrei Shur +author_email = amshoor@gmail.com +description = Church Slavonic script numbers conversion +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/endrain/cu-numbers +keywords = church slavonic, conversion +license = MIT +classifiers = + Development Status :: 5 - Production/Stable + 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 + +[options] +include_package_data = True +packages = cunumbers +python_requires = >=3.7 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cunumbers.py b/tests/test_cunumbers.py new file mode 100644 index 0000000..f9a342a --- /dev/null +++ b/tests/test_cunumbers.py @@ -0,0 +1,84 @@ +import unittest, cunumbers +arab_to_cu = cunumbers.arab_to_cu +cu_to_arab = cunumbers.cu_to_arab + +class arab_to_cuTestCase(unittest.TestCase): + def test_arab_to_cu_digits(self): + self.assertEqual(arab_to_cu(1), "а҃") + self.assertEqual(arab_to_cu(9), "ѳ҃") + + def test_arab_to_cu_tens(self): + self.assertEqual(arab_to_cu(10), "і҃") + self.assertEqual(arab_to_cu(18), "и҃і") + self.assertEqual(arab_to_cu(22), "к҃в") + + def test_arab_to_cu_hundreds(self): + self.assertEqual(arab_to_cu(100), "р҃") + self.assertEqual(arab_to_cu(207), "с҃з") + self.assertEqual(arab_to_cu(333), "тл҃г") + + def test_arab_to_cu_thousand(self): + self.assertEqual(arab_to_cu(1000), "҂а҃") + self.assertEqual(arab_to_cu(1006), "҂аѕ҃") + self.assertEqual(arab_to_cu(1015), "҂ає҃і") + self.assertEqual(arab_to_cu(1444), "҂аум҃д") + + def test_arab_to_cu_big(self): + self.assertEqual(arab_to_cu(10001010001), "҂҂҂і҂҂а҂іа҃") + self.assertEqual(arab_to_cu(50000000000), "҂҂҂н҃") + self.assertEqual(arab_to_cu(60000070000), "҂҂҂ѯ҂ѻ҃") + +class cu_to_arabTestCase(unittest.TestCase): + def test_cu_to_arab_digits(self): + self.assertEqual(1, cu_to_arab("а҃")) + self.assertEqual(9, cu_to_arab("ѳ")) + + def test_cu_to_arab_tens(self): + self.assertEqual(10, cu_to_arab("і҃")) + self.assertEqual(18, cu_to_arab("и҃і")) + self.assertEqual(22, cu_to_arab("к҃в")) + + def test_cu_to_arab_hundreds(self): + self.assertEqual(100, cu_to_arab("р҃")) + self.assertEqual(207, cu_to_arab("с҃з")) + self.assertEqual(333, cu_to_arab("тл҃г")) + + def test_cu_to_arab_thousands(self): + self.assertEqual(1000, cu_to_arab("҂а҃")) + self.assertEqual(1006, cu_to_arab("҂аѕ҃")) + self.assertEqual(1015, cu_to_arab("҂ає҃і")) + self.assertEqual(1444, cu_to_arab("҂аум҃д")) + + def test_cu_to_arab_big(self): + self.assertEqual(10001010001, cu_to_arab("҂҂҂і҂҂а҂іа҃")) + self.assertEqual(50000000000, cu_to_arab("҂҂҂н҃")) + self.assertEqual(60000070000, cu_to_arab("҂҂҂ѯ҂ѻ҃")) + + def test_cu_to_arab_no_tsnd(self): + self.assertEqual(80500690700, cu_to_arab("пфхчѱ҃")) + + def test_cu_to_arab_no_titlo(self): + self.assertEqual(1, cu_to_arab("а")) + + def test_cu_to_arab_spaced(self): + self.assertEqual(1, cu_to_arab("а҃ ")) + + def test_cu_to_arab_uppercase(self): + self.assertEqual(1, cu_to_arab("А҃")) + + def test_cu_to_arab_mixed(self): + self.assertEqual(2021, cu_to_arab(" вКА")) + +class ErrorTestCase(unittest.TestCase): + def test_arab_to_cu_error(self): + self.assertRaises(TypeError, arab_to_cu, "String") + self.assertRaises(TypeError, arab_to_cu, 9.75) + self.assertRaises(ValueError, arab_to_cu, 0) + self.assertRaises(ValueError, arab_to_cu, -69) + + def test_cu_to_arab_error(self): + self.assertRaises(TypeError, cu_to_arab, 420) + self.assertRaises(ValueError, cu_to_arab, "A113") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file