pax_global_header00006660000000000000000000000064147416543750014532gustar00rootroot0000000000000052 comment=ea8693661a68be5e11fd9978cd99d4ed115e8f30 roman-5.0/000077500000000000000000000000001474165437500125125ustar00rootroot00000000000000roman-5.0/.editorconfig000066400000000000000000000020101474165437500151600ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python # # EditorConfig Configuration file, for more details see: # http://EditorConfig.org # EditorConfig is a convention description, that could be interpreted # by multiple editors to enforce common coding conventions for specific # file types # top-most EditorConfig file: # Will ignore other EditorConfig files in Home directory or upper tree level. root = true [*] # For All Files # Unix-style newlines with a newline ending every file end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true # Set default charset charset = utf-8 # Indent style default indent_style = space # Max Line Length - a hard line wrap, should be disabled max_line_length = off [*.{py,cfg,ini}] # 4 space indentation indent_size = 4 [*.{yml,zpt,pt,dtml,zcml}] # 2 space indentation indent_size = 2 [{Makefile,.gitmodules}] # Tab indentation (no size specified, but view as 4 spaces) indent_style = tab indent_size = unset tab_width = unset roman-5.0/.github/000077500000000000000000000000001474165437500140525ustar00rootroot00000000000000roman-5.0/.github/workflows/000077500000000000000000000000001474165437500161075ustar00rootroot00000000000000roman-5.0/.github/workflows/pre-commit.yml000066400000000000000000000013231474165437500207050ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python name: pre-commit on: pull_request: push: branches: - master # Allow to run this workflow manually from the Actions tab workflow_dispatch: env: FORCE_COLOR: 1 jobs: pre-commit: name: linting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x - uses: pre-commit/action@v3.0.1 with: extra_args: --all-files --show-diff-on-failure env: PRE_COMMIT_COLOR: always - uses: pre-commit-ci/lite-action@v1.1.0 if: always() with: msg: Apply pre-commit code formatting roman-5.0/.github/workflows/tests.yml000066400000000000000000000037221474165437500200000ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python name: tests on: push: pull_request: schedule: - cron: '0 12 * * 0' # run once a week on Sunday # Allow to run this workflow manually from the Actions tab workflow_dispatch: jobs: build: strategy: # We want to see all failures: fail-fast: false matrix: os: - ["ubuntu", "ubuntu-latest"] config: # [Python version, tox env] - ["3.11", "release-check"] - ["3.9", "py39"] - ["3.10", "py310"] - ["3.11", "py311"] - ["3.12", "py312"] - ["3.13", "py313"] - ["3.14", "py314"] - ["pypy-3.10", "pypy3"] - ["3.11", "coverage"] runs-on: ${{ matrix.os[1] }} if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.config[0] }} allow-prereleases: true - name: Pip cache uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.config[0] }}- ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox - name: Test if: ${{ !startsWith(runner.os, 'Mac') }} run: tox -e ${{ matrix.config[1] }} - name: Test (macOS) if: ${{ startsWith(runner.os, 'Mac') }} run: tox -e ${{ matrix.config[1] }}-universal2 - name: Coverage if: matrix.config[1] == 'coverage' run: | pip install coveralls coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} roman-5.0/.gitignore000066400000000000000000000005411474165437500145020ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python *.dll *.egg-info/ *.profraw *.pyc *.pyo *.so .coverage .coverage.* .eggs/ .installed.cfg .mr.developer.cfg .tox/ .vscode/ __pycache__/ bin/ build/ coverage.xml develop-eggs/ develop/ dist/ docs/_build eggs/ etc/ lib/ lib64 log/ parts/ pyvenv.cfg testing.log var/ roman-5.0/.meta.toml000066400000000000000000000006011474165437500144100ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" commit-id = "886d86ee" [python] with-sphinx-doctests = false with-future-python = true with-pypy = true with-macos = false with-windows = false with-docs = false [tox] use-flake8 = true testenv-deps = [ "zope.testrunner", ] [coverage] fail-under = 100 roman-5.0/.pre-commit-config.yaml000066400000000000000000000013271474165437500167760ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python minimum_pre_commit_version: '3.6' repos: - repo: https://github.com/pycqa/isort rev: "5.13.2" hooks: - id: isort - repo: https://github.com/hhatto/autopep8 rev: "v2.3.1" hooks: - id: autopep8 args: [--in-place, --aggressive, --aggressive] - repo: https://github.com/asottile/pyupgrade rev: v3.19.0 hooks: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/isidentical/teyit rev: 0.4.3 hooks: - id: teyit - repo: https://github.com/PyCQA/flake8 rev: "7.1.1" hooks: - id: flake8 additional_dependencies: - flake8-debugger == 4.1.2 roman-5.0/CHANGES.rst000066400000000000000000000025631474165437500143220ustar00rootroot00000000000000Change log ========== 5.0 (2025-01-15) ---------------- - Drop support for Python 3.7, 3.8. - Add support for lower case roman numerals. (`#22 `_) 4.2 (2024-04-25) ---------------- - Remove overlooked mentions of the Python 2.1.1 license (`#17 `_) - Add support for Python 3.12 and 3.13. 4.1 (2023-05-26) ---------------- - Change license to the Zope Public License (ZPL) version 2.1 (`#15 `_) 4.0 (2023-02-28) ---------------- - Add support for Python 3.10, 3.11. - Drop support for Python 2.7, 3.5, 3.6. 3.3 (2020-07-12) ---------------- - added support for Python 3.9 - added CLI command ``roman`` with ``-r/--reverse`` to convert back from Roman - added simple usage instructions 3.2 (2019-04-14) ---------------- - expanded test coverage - Added support for 0 -> N (see https://en.wikipedia.org/wiki/Roman_numerals#Zero) - Added support for Python 3.8 3.1 (2018-10-24) ---------------- - Added support for Python 3.7. 3.0 (2018-05-28) ---------------- - Added support for Python 3.5, 3.6 and PyPy3. - Dropped support for Python 2.6 and 3.3. 2.0.0 (2013-02-25) ------------------ - Added Python 3.3 and PyPy support. - Added tests. 1.4.0 (2009-07-23) ------------------ - Initial PyPI release. roman-5.0/CONTRIBUTING.md000066400000000000000000000014441474165437500147460ustar00rootroot00000000000000 # Contributing to zopefoundation projects The projects under the zopefoundation GitHub organization are open source and welcome contributions in different forms: * bug reports * code improvements and bug fixes * documentation improvements * pull request reviews For any changes in the repository besides trivial typo fixes you are required to sign the contributor agreement. See https://www.zope.dev/developer/becoming-a-committer.html for details. Please visit our [Developer Guidelines](https://www.zope.dev/developer/guidelines.html) if you'd like to contribute code changes and our [guidelines for reporting bugs](https://www.zope.dev/developer/reporting-bugs.html) if you want to file a bug report. roman-5.0/COPYRIGHT.txt000066400000000000000000000000361474165437500146220ustar00rootroot00000000000000Mark Pilgrim and Contributors roman-5.0/LICENSE.txt000066400000000000000000000040261474165437500143370ustar00rootroot00000000000000Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. roman-5.0/MANIFEST.in000066400000000000000000000003441474165437500142510ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python include *.md include *.rst include *.txt include buildout.cfg include tox.ini include .pre-commit-config.yaml recursive-include src *.py roman-5.0/README.rst000066400000000000000000000022461474165437500142050ustar00rootroot00000000000000.. image:: https://github.com/zopefoundation/roman/actions/workflows/tests.yml/badge.svg :target: https://github.com/zopefoundation/roman/actions/workflows/tests.yml .. image:: https://coveralls.io/repos/github/zopefoundation/roman/badge.svg?branch=master :target: https://coveralls.io/github/zopefoundation/roman?branch=master .. image:: https://img.shields.io/pypi/v/roman.svg :target: https://pypi.org/project/roman/ :alt: Current version on PyPI .. image:: https://img.shields.io/pypi/pyversions/roman.svg :target: https://pypi.org/project/roman/ :alt: Supported Python versions roman ===== Small helper library to convert arabic to roman numerals. There are two ways to use this library. 1. Importing it into your application .. code-block:: python import roman # to roman number = int(input('> ')) # 10 print(roman.toRoman(number)) # from roman number = input('> ') # X print(roman.fromRoman(number)) 2. ``roman`` CLI command .. code-block:: bash ~$ roman 972 CMLXXII # use the -r/--reverse to convert Roman numerals ~$ roman -r CMLXXII 972 # case insensitive ~$ roman -r cMlxxii 972 roman-5.0/pyproject.toml000066400000000000000000000011071474165437500154250ustar00rootroot00000000000000# # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [build-system] requires = ["setuptools <= 75.6.0"] build-backend = "setuptools.build_meta" [tool.coverage.run] branch = true source = ["roman"] [tool.coverage.report] fail_under = 100 precision = 2 ignore_errors = true show_missing = true exclude_lines = ["pragma: no cover", "pragma: nocover", "except ImportError:", "raise NotImplementedError", "if __name__ == '__main__':", "self.fail", "raise AssertionError", "raise unittest.Skip"] [tool.coverage.html] directory = "parts/htmlcov" roman-5.0/setup.cfg000066400000000000000000000006741474165437500143420ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [flake8] doctests = 1 [check-manifest] ignore = .editorconfig .meta.toml [isort] force_single_line = True combine_as_imports = True sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER known_third_party = docutils, pkg_resources, pytz known_zope = known_first_party = default_section = ZOPE line_length = 79 lines_after_imports = 2 roman-5.0/setup.py000066400000000000000000000042051474165437500142250ustar00rootroot00000000000000############################################################################## # # Copyright (c) 2001 Mark Pilgrim and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## from setuptools import setup desc = ('{}\n\n{}'.format(open('README.rst').read(), open('CHANGES.rst').read())) setup( name='roman', version='5.0', author="Mark Pilgrim", maintainer="Zope Foundation and Contributors", maintainer_email="zope-dev@zope.dev", description="Integer to Roman numerals converter", long_description=desc, long_description_content_type='text/x-rst', license="ZPL 2.1", keywords="roman", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', ], url='https://github.com/zopefoundation/roman', package_dir={"": "src"}, python_requires='>=3.9', py_modules=["roman"], include_package_data=True, zip_safe=True, entry_points={ 'console_scripts': [ 'roman=roman:main', ] } ) roman-5.0/src/000077500000000000000000000000001474165437500133015ustar00rootroot00000000000000roman-5.0/src/roman.py000066400000000000000000000077641474165437500150050ustar00rootroot00000000000000############################################################################## # # Copyright (c) 2001 Mark Pilgrim and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Convert to and from Roman numerals""" __author__ = "Mark Pilgrim (f8dy@diveintopython.org)" __copyright__ = """Copyright (c) 2001 Mark Pilgrim This program is part of "Dive Into Python", a free Python tutorial for experienced programmers. Visit http://diveintopython.org/ for the latest version. """ import argparse import re import sys # Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass # Define digit mapping romanNumeralMap = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)) def toRoman(n): """convert integer to Roman numeral""" if not isinstance(n, int): raise NotIntegerError("decimals cannot be converted") if not (-1 < n < 5000): raise OutOfRangeError("number out of range (must be 0..4999)") # special case if n == 0: return 'N' result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result # Define pattern to detect valid Roman numerals romanNumeralPattern = re.compile(""" ^ # beginning of string M{0,4} # thousands - 0 to 4 M's (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), # or 500-800 (D, followed by 0 to 3 C's) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), # or 50-80 (L, followed by 0 to 3 X's) (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), # or 5-8 (V, followed by 0 to 3 I's) $ # end of string """, re.VERBOSE) def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError('Input cannot be blank') s = s.upper() # Handle lowercase inputs # special case if s == 'N': return 0 if not romanNumeralPattern.search(s): raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index + len(numeral)] == numeral: result += integer index += len(numeral) return result def parse_args(): parser = argparse.ArgumentParser( prog='roman', description='convert between roman and arabic numerals' ) parser.add_argument('number', help='the value to convert') parser.add_argument( '-r', '--reverse', action='store_true', default=False, help='convert roman to numeral (case insensitive) [default: False]') args = parser.parse_args() args.number = args.number return args def main(): args = parse_args() if args.reverse: r = fromRoman(args.number) print(r) else: i = int(args.number) n = toRoman(i) print(n) return 0 if __name__ == "__main__": # pragma: no cover sys.exit(main()) # pragma: no cover roman-5.0/src/tests.py000066400000000000000000000063111474165437500150160ustar00rootroot00000000000000############################################################################## # # Copyright (c) 2001 Mark Pilgrim and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import os import sys import unittest from io import StringIO import roman TEST_MAP = ((0, 'N'), (1, 'I'), (3, 'III'), (4, 'IV'), (9, 'IX'), (14, 'XIV'), (19, 'XIX'), (24, 'XXIV'), (40, 'XL'), (49, 'XLIX'), (90, 'XC'), (99, 'XCIX'), (400, 'CD'), (490, 'CDXC'), (499, 'CDXCIX'), (900, 'CM'), (990, 'CMXC'), (998, 'CMXCVIII'), (999, 'CMXCIX'), (2013, 'MMXIII')) class TestRoman(unittest.TestCase): def test_toRoman(self): for num_arabic, num_roman in TEST_MAP: self.assertEqual(roman.toRoman(num_arabic), num_roman, f'{num_arabic} should be {num_roman}') def test_toRoman_errors(self): self.assertRaises(roman.OutOfRangeError, roman.toRoman, 100000) self.assertRaises(roman.NotIntegerError, roman.toRoman, '1') def test_fromRoman(self): for num_arabic, num_roman in TEST_MAP: self.assertEqual(roman.fromRoman(num_roman), num_arabic, f'{num_roman} should be {num_arabic}') def test_fromRoman_errors(self): self.assertRaises( roman.InvalidRomanNumeralError, roman.fromRoman, '') self.assertRaises( roman.InvalidRomanNumeralError, roman.fromRoman, 'Q12') def test_parse_args(self): sys.argv = ['roman', '10'] args = roman.parse_args() self.assertFalse(args.reverse) self.assertEqual(args.number, '10') def test_main_toRoman(self): for num_arabic, num_roman in TEST_MAP: sys.argv = ['roman', str(num_arabic)] sys.stdout = StringIO() ex_st = roman.main() output = sys.stdout.getvalue().strip() self.assertEqual(output, num_roman) self.assertEqual(ex_st, os.EX_OK) def test_main_fromRoman(self): for num_arabic, num_roman in TEST_MAP: sys.argv = ['roman', '--reverse', num_roman] sys.stdout = StringIO() ex_st = roman.main() output = sys.stdout.getvalue().strip() self.assertEqual(output, str(num_arabic)) self.assertEqual(ex_st, os.EX_OK) def test_main_fromRoman_caseInsensitive(self): for num_arabic, num_roman in TEST_MAP: sys.argv = ['roman', '--reverse', num_roman.lower()] sys.stdout = StringIO() ex_st = roman.main() output = sys.stdout.getvalue().strip() self.assertEqual(output, str(num_arabic)) self.assertEqual(ex_st, os.EX_OK) def test_suite(): return unittest.defaultTestLoader.loadTestsFromTestCase(TestRoman) roman-5.0/tox.ini000066400000000000000000000030031474165437500140210ustar00rootroot00000000000000# Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [tox] minversion = 3.18 envlist = release-check lint py39 py310 py311 py312 py313 py314 pypy3 coverage [testenv] usedevelop = true package = wheel wheel_build_env = .pkg pip_pre = py314: true deps = setuptools <= 75.6.0 zope.testrunner commands = zope-testrunner --test-path=src {posargs:-vc} extras = test [testenv:setuptools-latest] basepython = python3 deps = git+https://github.com/pypa/setuptools.git\#egg=setuptools zope.testrunner [testenv:release-check] description = ensure that the distribution is ready to release basepython = python3 skip_install = true deps = setuptools <= 75.6.0 twine build check-manifest check-python-versions >= 0.20.0 wheel commands_pre = commands = check-manifest check-python-versions --only setup.py,tox.ini,.github/workflows/tests.yml python -m build --sdist --no-isolation twine check dist/* [testenv:lint] description = This env runs all linters configured in .pre-commit-config.yaml basepython = python3 skip_install = true deps = pre-commit commands_pre = commands = pre-commit run --all-files --show-diff-on-failure [testenv:coverage] basepython = python3 allowlist_externals = mkdir deps = coverage[toml] zope.testrunner commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage html coverage report