3 Input handling routines
4 Copyright 2014 Cray Inc. All Rights Reserved
11 # Define some common integer multiplier suffixes
15 'k': 2**10, 'K': 2**10,
16 'm': 2**20, 'M': 2**20,
17 'g': 2**30, 'G': 2**30,
18 't': 2**40, 'T': 2**40,
19 'p': 2**50, 'P': 2**50
21 memory_suffixes = binary_suffixes
25 'k': 10**3, 'K': 10**3,
26 'm': 10**6, 'M': 10**6,
27 'g': 10**9, 'G': 10**9,
28 't': 10**12, 'T': 10**12,
29 'p': 10**15, 'P': 10**15
31 disk_suffixes = decimal_suffixes
33 default_bases = [0, 16]
35 def toint(string, base=default_bases, suffixes=binary_suffixes):
36 """Convert to integer with flexible base and multiplier support.
38 Provide a way to handle input that may be in any of several number
39 bases but may not use the appropriate prefix, e.g. 'deadbeef' rather
40 than the more pedantic '0xdeadbeef'. Also provide support for
41 multiplier suffixes, such as 'K' for kilo.
45 string - string to convert to integer
46 base - a single number, as used in int() or an iterable
48 suffixes - dictionary keyed by the string suffix with a value
49 to be used as a multiplier
51 The default base of [0, 16] allows the automatic recognition of numbers
52 with the standard prefixes and if that fails, tries a base 16 conversion.
57 # Object isn't iterable, so create one that is
61 if not (b == 0 or 2 <= b <= 36):
63 "toint() base {!s:s} must be >= 2 and <= 36".format(b))
67 # Second iteration is after removing any suffix. This way, if
68 # a suffix happens to contain valid numeric characters, we'll
69 # try the numeric interpretation before we try their multiplier
70 # meaning, e.g. 'g' is a valid numeric value in base 17).
74 return int(string, b) * multiplier
81 # Find a suffix that matches the end of the string and use it
82 for k, v in suffixes.iteritems():
83 if string.endswith(k):
85 string = string[0:-len(k)]
91 suffix_list = suffixes.keys()
94 "invalid literal '{:s}' for toint() with base {!s:s} "
95 "and suffixes {!s:s}".format(string, list(bases), suffix_list))
99 """Wrapper for toint() which prefers base 16 input
101 This function is useful in situations where a callable must be passed,
102 such as with argparse.add_argument(type=hex2int, ...
104 return toint(string, base=[16, 0])
107 def to_rangelist(args, default=xrange(0), base=[0,16],
108 suffixes=binary_suffixes):
109 """Convert a bunch of range list strings into a list of ranges
113 args - iterable containing ranglist strings
114 default - iterator to return if args is empty
115 base - number base to use for integer conversion
116 suffixes - integer multiplier suffixes
118 Each arg is taken to be a range list, where a range list may be:
120 rangelist ::= range[,range]...
121 range ::= <first>-<last> | <first>#<count> | <value>
123 where the range first-last is inclusive.
129 for range_list_str in args:
130 range_strs = range_list_str.split(',')
131 for range_str in range_strs:
133 fields = range_str.split('-', 1)
134 start = toint(fields[0], base, suffixes=suffixes)
135 end = toint(fields[1], base, suffixes=suffixes) + 1
136 ranges.append(xrange(start, end))
137 elif "#" in range_str:
138 fields = range_str.split('#', 1)
139 start = toint(fields[0], base, suffixes=suffixes)
140 end = start + toint(fields[1], base, suffixes=suffixes)
141 ranges.append(xrange(start, end))
143 start = toint(range_str, base, suffixes=suffixes)
145 ranges.append(xrange(start, end))
150 def iter_rangestr(*args, **kwargs):
151 """Convert a bunch of range list strings into a single iterator
153 The arguments are the same as for to_rangelist().
155 return itertools.chain(*to_rangelist(*args, **kwargs))
158 if __name__ == '__main__':
162 class Test_toint(unittest.TestCase):
163 def test_base_zero(self):
164 self.assertEqual(toint('0b10', 0), 2)
165 self.assertEqual(toint('0o10', 0), 8)
166 self.assertEqual(toint('10', 0), 10)
167 self.assertEqual(toint('0x10', 0), 16)
169 def test_base_out_of_range(self):
170 self.assertRaises(ValueError, toint, '10', -1)
171 self.assertRaises(ValueError, toint, '10', 1)
172 self.assertRaises(ValueError, toint, '10', 37)
174 def test_base_search(self):
176 self.assertEqual(toint('10', bases), 10)
177 self.assertEqual(toint('f', bases), 15)
179 self.assertEqual(toint('0b10', bases), 2)
180 self.assertEqual(toint('0o10', bases), 8)
181 self.assertEqual(toint('10', bases), 10)
182 self.assertEqual(toint('0x10', bases), 16)
184 def test_suffixes(self):
185 for k, v in binary_suffixes.iteritems():
186 self.assertEqual(toint('0b10'+k), 0b10*v)
187 self.assertEqual(toint('0o10'+k), 0o10*v)
188 self.assertEqual(toint('10'+k), 10*v)
189 self.assertEqual(toint('0x10'+k), 0x10*v)
191 def test_suffix_number_overlap(self):
192 # Verify a valid numeric isn't used as a suffix
193 self.assertEqual(toint('1g', 17), 33)
194 self.assertEqual(toint('1gk', 17), 33*binary_suffixes['k'])
198 class Test_hex2int(unittest.TestCase):
199 """Verify the hex2int() function"""
200 def test_explicit_base(self):
201 """Verify that explicit base syntax is honored"""
202 self.assertEqual(hex2int('0x10'), 16)
203 self.assertEqual(hex2int('0o10'), 8)
205 def test_default_base(self):
206 """Verify that base 16 is preferred"""
207 self.assertEqual(hex2int('10'), 16)
208 self.assertEqual(hex2int('0b10'), 2832)
212 class Test_iter_rangelist(unittest.TestCase):
213 """Test both iter_rangelist and the underlying to_rangelist."""
214 def test_good_single_ranges(self):
215 self.assertEqual(list(iter_rangestr([])), [])
216 self.assertEqual(list(iter_rangestr(['1-2'])), list(xrange(1,3)))
217 self.assertEqual(list(iter_rangestr(['1#2'])), list(xrange(1,3)))
218 self.assertEqual(list(iter_rangestr(['1'])), list(xrange(1,2)))
220 def test_good_multiple_ranges(self):
222 # Test params, Expected result
223 (['1', '3-5', '1#2'], [1, 3, 4, 5, 1, 2]),
226 for ranges, expected in test_rangestrs:
227 # Test the ranges as separate list elements
228 self.assertEqual(list(iter_rangestr(ranges)), expected)
230 # Test the ranges joined by commas
231 joined = [','.join(ranges)]
232 self.assertEqual(list(iter_rangestr(joined)), expected)
234 def test_bad_single_ranges(self):
235 self.assertRaises(ValueError, iter_rangestr, ['1#2#3'])
236 self.assertRaises(ValueError, iter_rangestr, ['1#2-3'])
237 self.assertRaises(ValueError, iter_rangestr, ['1-2#3'])
238 self.assertRaises(ValueError, iter_rangestr, ['1-2-3'])