pax_global_header00006660000000000000000000000064137026250420014513gustar00rootroot0000000000000052 comment=1dfec12ae52f7069e67468ca193d5779414d7be3 roman-3.3/000077500000000000000000000000001370262504200124745ustar00rootroot00000000000000roman-3.3/.gitignore000066400000000000000000000002571370262504200144700ustar00rootroot00000000000000*.pyc __pycache__ src/*.egg-info benchmark/*.egg-info pyvenv.cfg .coverage* .installed.cfg .tox bin build coverage.xml default.profraw develop-eggs dist docs lib lib64 parts roman-3.3/.travis.yml000066400000000000000000000013221370262504200146030ustar00rootroot00000000000000language: python matrix: include: - python: "2.7" env: TOXENV=py27 - python: "3.5" env: TOXENV=py35 - python: "3.6" env: TOXENV=py36 - python: "3.7" env: TOXENV=py37 dist: xenial - python: "3.8" env: TOXENV=py38 - python: "3.9-dev" env: TOXENV=py39 - python: "pypy" env: TOXENV=pypy - python: "pypy3" env: TOXENV=pypy3 install: - pip install -U pip setuptools - pip install -U tox coveralls coverage script: - tox after_success: - coverage combine - coveralls notifications: email: false cache: pip: true directories: - eggs/ roman-3.3/CHANGES.txt000066400000000000000000000013431370262504200143060ustar00rootroot00000000000000Change log ========== 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-3.3/MANIFEST.in000066400000000000000000000001751370262504200142350ustar00rootroot00000000000000include *.rst include *.txt include *.py include buildout.cfg include tox.ini recursive-include src * global-exclude *.pyc roman-3.3/README.rst000066400000000000000000000021661370262504200141700ustar00rootroot00000000000000.. image:: https://travis-ci.org/zopefoundation/roman.svg?branch=master :target: https://travis-ci.org/zopefoundation/roman .. 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 conver Roman numerals ~$ roman -r CMLXXII 972 # case insensitive ~$ roman -r cMlxxii 972 roman-3.3/bootstrap.py000066400000000000000000000145451370262504200150740ustar00rootroot00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation 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. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os import shutil import sys import tempfile from optparse import OptionParser tmpeggs = tempfile.mkdtemp() usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", help="use a specific zc.buildout version") parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used.")) parser.add_option("-f", "--find-links", help=("Specify a URL to search for buildout releases")) parser.add_option("--allow-site-packages", action="store_true", default=False, help=("Let bootstrap.py use existing site packages")) parser.add_option("--setuptools-version", help="use a specific setuptools version") options, args = parser.parse_args() ###################################################################### # load/install setuptools try: if options.allow_site_packages: import setuptools import pkg_resources from urllib.request import urlopen except ImportError: from urllib2 import urlopen ez = {} exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) if not options.allow_site_packages: # ez_setup imports site, which adds site packages # this will remove them from the path to ensure that incompatible versions # of setuptools are not in the path import site # inside a virtualenv, there is no 'getsitepackages'. # We can't remove these reliably if hasattr(site, 'getsitepackages'): for sitepackage_path in site.getsitepackages(): sys.path[:] = [x for x in sys.path if sitepackage_path not in x] setup_args = dict(to_dir=tmpeggs, download_delay=0) if options.setuptools_version is not None: setup_args['version'] = options.setuptools_version ez['use_setuptools'](**setup_args) import setuptools import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set cmd = [sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] find_links = os.environ.get( 'bootstrap-testing-find-links', options.find_links or ('http://downloads.buildout.org/' if options.accept_buildout_test_releases else None) ) if find_links: cmd.extend(['-f', find_links]) setuptools_path = ws.find( pkg_resources.Requirement.parse('setuptools')).location requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): try: return not parsed_version.is_prerelease except AttributeError: # Older setuptools for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: raise Exception( "Failed to execute command:\n%s" % repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if '=' not in a]: args.append('bootstrap') # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ['-c', options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) roman-3.3/buildout.cfg000066400000000000000000000001331370262504200150010ustar00rootroot00000000000000[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = roman roman-3.3/setup.cfg000066400000000000000000000006101370262504200143120ustar00rootroot00000000000000[bdist_wheel] universal = 1 [isort] force_single_line = True combine_as_imports = True line_length = 79 lines_after_imports = 2 [flake8] no-accept-encodings = True exclude = bootstrap.py [coverage:run] branch = True source = src omit = [coverage:report] fail_under = 95.0 ignore_errors = True precision = 2 show_missing = False sort = Name [coverage:html] directory = parts/coverage roman-3.3/setup.py000066400000000000000000000030661370262504200142130ustar00rootroot00000000000000from setuptools import setup desc = ('%s/n/n%s' % (open('README.rst').read(), open('CHANGES.txt').read())) setup( name='roman', version='3.3', author="Mark Pilgrim", author_email="f8dy@diveintopython.org", description="Integer to Roman numerals converter", long_description=desc, license="Python 2.1.1", keywords="roman", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', '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 :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: Python Software Foundation License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent'], url='https://github.com/zopefoundation/roman', package_dir={"": "src"}, python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', py_modules=["roman"], include_package_data=True, test_suite='tests', zip_safe=True, entry_points={ 'console_scripts': [ 'roman=roman:main', ] } ) roman-3.3/src/000077500000000000000000000000001370262504200132635ustar00rootroot00000000000000roman-3.3/src/roman.py000066400000000000000000000071201370262504200147510ustar00rootroot00000000000000from __future__ import print_function """Convert to and from Roman numerals""" __author__ = "Mark Pilgrim (f8dy@diveintopython.org)" __version__ = "1.4" __date__ = "8 August 2001" __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. This program is free software; you can redistribute it and/or modify it under the terms of the Python 2.1.1 license, available at http://www.python.org/2.1.1/license.html """ import argparse import os 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 can not 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 can not be blank') # 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: u = args.number.upper() r = fromRoman(u) print(r) else: i = int(args.number) n = toRoman(i) print(n) return os.EX_OK if __name__ == "__main__": sys.exit(main()) roman-3.3/src/tests.py000066400000000000000000000053361370262504200150060ustar00rootroot00000000000000import os import sys import unittest if sys.version_info[0] > 2: from io import StringIO else: from StringIO 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')) if sys.version_info[0] > 2: _str = str else: _str = unicode # NOQA: F821 class TestRoman(unittest.TestCase): def test_toRoman(self): for num_arabic, num_roman in TEST_MAP: self.assertEqual(roman.toRoman(num_arabic), num_roman, '%s should be %s' % (num_arabic, 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, '%s should be %s' % (num_roman, 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.makeSuite(TestRoman) roman-3.3/tox.ini000066400000000000000000000025311370262504200140100ustar00rootroot00000000000000[tox] envlist = py27, py35, py36, py37, py38, py39, pypy, pypy3, lint, coverage [testenv] commands = {envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test coverage run {envbindir}/test {posargs:-cv} deps = zc.buildout coverage setenv = COVERAGE_FILE=.coverage.{envname} skip_install = true [testenv:coverage] basepython = python3.6 skip_install = true deps = coverage depends = py27, py35, py36, py37, py38, py39, pypy, pypy3, setenv = COVERAGE_FILE=.coverage commands = coverage erase coverage combine coverage html coverage xml coverage report [testenv:lint] basepython = python3.6 skip_install = true deps = isort flake8 # helper to generate HTML reports: flake8-html # Useful flake8 plugins that are Python and Plone specific: flake8-coding flake8-debugger flake8-deprecated flake8-todo mccabe # Potential flake8 plugins that should be used: # TBD #flake8-blind-except #flake8-commas #flake8-docstrings #flake8-mypy #flake8-pep3101 #flake8-plone-hasattr #flake8-string-format #flake8_strict #flake8-quotes commands = - isort --check-only --diff {toxinidir}/src setup.py flake8 src setup.py