Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/docutils/utils/roman.py: 52%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

62 statements  

1############################################################################## 

2# 

3# Copyright (c) 2001 Mark Pilgrim and Contributors. 

4# All Rights Reserved. 

5# 

6# This software is subject to the provisions of the Zope Public License, 

7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 

8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 

9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 

11# FOR A PARTICULAR PURPOSE. 

12# 

13############################################################################## 

14"""Convert to and from Roman numerals""" 

15 

16__author__ = "Mark Pilgrim (f8dy@diveintopython.org)" 

17__version__ = "1.4" 

18__date__ = "8 August 2001" 

19__copyright__ = """Copyright (c) 2001 Mark Pilgrim 

20 

21This program is part of "Dive Into Python", a free Python tutorial for 

22experienced programmers. Visit http://diveintopython.org/ for the 

23latest version. 

24 

25This program is free software; you can redistribute it and/or modify 

26it under the terms of the Python 2.1.1 license, available at 

27http://www.python.org/2.1.1/license.html 

28""" 

29 

30import argparse 

31import re 

32import sys 

33 

34 

35# Define exceptions 

36class RomanError(Exception): 

37 pass 

38 

39 

40class OutOfRangeError(RomanError): 

41 pass 

42 

43 

44class NotIntegerError(RomanError): 

45 pass 

46 

47 

48class InvalidRomanNumeralError(RomanError): 

49 pass 

50 

51 

52# Define digit mapping 

53romanNumeralMap = (('M', 1000), 

54 ('CM', 900), 

55 ('D', 500), 

56 ('CD', 400), 

57 ('C', 100), 

58 ('XC', 90), 

59 ('L', 50), 

60 ('XL', 40), 

61 ('X', 10), 

62 ('IX', 9), 

63 ('V', 5), 

64 ('IV', 4), 

65 ('I', 1)) 

66 

67 

68def toRoman(n): 

69 """convert integer to Roman numeral""" 

70 if not isinstance(n, int): 

71 raise NotIntegerError("decimals can not be converted") 

72 if not (-1 < n < 5000): 

73 raise OutOfRangeError("number out of range (must be 0..4999)") 

74 

75 # special case 

76 if n == 0: 

77 return 'N' 

78 

79 result = "" 

80 for numeral, integer in romanNumeralMap: 

81 while n >= integer: 

82 result += numeral 

83 n -= integer 

84 return result 

85 

86 

87# Define pattern to detect valid Roman numerals 

88romanNumeralPattern = re.compile(""" 

89 ^ # beginning of string 

90 M{0,4} # thousands - 0 to 4 M's 

91 (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), 

92 # or 500-800 (D, followed by 0 to 3 C's) 

93 (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), 

94 # or 50-80 (L, followed by 0 to 3 X's) 

95 (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), 

96 # or 5-8 (V, followed by 0 to 3 I's) 

97 $ # end of string 

98 """, re.VERBOSE) 

99 

100 

101def fromRoman(s): 

102 """convert Roman numeral to integer""" 

103 if not s: 

104 raise InvalidRomanNumeralError('Input can not be blank') 

105 

106 # special case 

107 if s == 'N': 

108 return 0 

109 

110 if not romanNumeralPattern.search(s): 

111 raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) 

112 

113 result = 0 

114 index = 0 

115 for numeral, integer in romanNumeralMap: 

116 while s[index:index + len(numeral)] == numeral: 

117 result += integer 

118 index += len(numeral) 

119 return result 

120 

121 

122def parse_args(): 

123 parser = argparse.ArgumentParser( 

124 prog='roman', 

125 description='convert between roman and arabic numerals' 

126 ) 

127 parser.add_argument('number', help='the value to convert') 

128 parser.add_argument( 

129 '-r', '--reverse', 

130 action='store_true', 

131 default=False, 

132 help='convert roman to numeral (case insensitive) [default: False]') 

133 

134 args = parser.parse_args() 

135 args.number = args.number 

136 return args 

137 

138 

139def main(): 

140 args = parse_args() 

141 if args.reverse: 

142 u = args.number.upper() 

143 r = fromRoman(u) 

144 print(r) 

145 else: 

146 i = int(args.number) 

147 n = toRoman(i) 

148 print(n) 

149 

150 return 0 

151 

152 

153if __name__ == "__main__": # pragma: no cover 

154 sys.exit(main()) # pragma: no cover