Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
#
# Copyright (C) 2009 Vinay Sajip.
# Licensed under the PSF licence.
# Exploratory code to convert %-format strings to {}-format.
#
import re
PATTERN = re.compile(r'''
% # specifier start
(\((?P<key>[^)]+)\))? # optional mapping key
(?P<flags>[#+0 -]*) # conversion flags
(?P<min_width>(\*|([1-9][0-9]*)))? # minimum width
(\.(?P<precision>(\*|([1-9][0-9]*))))? # precision
(?P<len_modifier>[hlL])? # length modifier
(?P<conversion>[diouxXeEfFgGcrs%]) # conversion
''', re.VERBOSE)
class PercentConversion:
'''Represents a single conversion group.'''
def __init__(self, converter, match):
'''
Initialize using the regex match information.
The converter is passed because we need to keep track of the
positional argument index, so that we can support minimum width
and precision values of '*'.
'''
self.source = match.group(0)
d = match.groupdict()
self.span = match.span()
s = d['key']
if s is None:
self.key = converter.next_index()
else:
self.key = s
s = d['min_width']
if s == '*':
#TODO {} representation is hard-wired, could generalise this
#if needed.
self.min_width = '{%s}' % converter.next_index()
else:
self.min_width = s
s = d['precision']
if s == '*':
self.precision = '{%s}' % converter.next_index()
else:
self.precision = s
#len_modifier not used
#self.len_modifier = d['len_modifier']
self.flags = d['flags']
self.conversion = d['conversion']
def as_brace(self):
'''Return representation of this conversion group for {}-formatting.'''
#TODO: '#', ' ' flags not handled
conversion = self.conversion
if conversion == '%':
result = '%'
else:
key = self.key
flags = self.flags
if self.min_width is None:
align = ''
elif '-' in flags:
align = '' # could use '<', but as it's the default...
elif '0' in flags and conversion in 'diouxX':
align = '='
else:
align = '>'
sign = ''
if '+' in flags:
sign = '+'
elif ' ' in flags:
sign = ' '
alt = ''
fill = ''
if '0' in flags and '-' not in flags and conversion not in 'crs':
fill = '0'
if '#' in flags and conversion in 'diuxX': # exclude 'o'
alt = '#'
transform = ''
if conversion in 'iu':
conversion = 'd'
#%s is interpreted as calling str() on the operand, so
#we specify !s in the {}-format. If we don't do this, then
#we can't convert the case where %s is used to print e.g. integers
#or floats.
elif conversion in 'rs':
transform = '!' + conversion
conversion = ''
prefix = '%s%s' % (key, transform)
suffix = '%s%s%s%s' % (fill, align, sign, alt)
if self.min_width:
suffix += self.min_width
if self.precision:
suffix += '.' + self.precision
suffix += conversion
result = prefix
if suffix:
result += ':' + suffix
result = '{%s}' % result
return result
class PercentToBraceConverter:
'''Utility class to convert a %-format string to a {}-format string.'''
def __init__(self, percent_format):
self.initial = percent_format
self.argindex = 0
def next_index(self):
'''Get the next argument index.'''
result = '%d' % self.argindex
self.argindex += 1
return result
def convert(self):
'''Perform the conversion, and return its result.'''
s = self.initial.replace('{', '{{').replace('}', '}}')
result = [c for c in s] # convert to mutable form
matches = [m for m in PATTERN.finditer(s)]
conversions = []
if matches:
for m in matches:
conversions.append(PercentConversion(self, m))
#Go backwards so that span values remain valid as we go
for conv in reversed(conversions):
s, e = conv.span
result[s:e] = conv.as_brace()
return ''.join(result), conversions
def main():
'''Quick-n-dirty test harness for PercentToBraceConverter.'''
import sys
from random import choice
if sys.version_info[0] == 3:
get_str = input
else:
get_str = raw_input
class Dummy:
def __str__(self):
return "I'm a dummy at %#x" % id(self)
INSTANCE_VALUE = Dummy()
INTEGER_VALUE = 0x1234
NEGATIVE_VALUE = -0x5678
FLOAT_VALUE = 3.14159265358979323846
LARGE_FLOAT_VALUE = 31415926535323846e108
NEGATIVE_FLOAT_VALUE = -2.71828182845904523536
LARGE_NEGATIVE_FLOAT_VALUE = -271828182845904523536e108
INT_VALUES = (INTEGER_VALUE, NEGATIVE_VALUE)
FLOAT_VALUES = (FLOAT_VALUE, NEGATIVE_FLOAT_VALUE, LARGE_FLOAT_VALUE, LARGE_NEGATIVE_FLOAT_VALUE)
STRING_VALUE = 'abcd'
STRING_VALUES = (INTEGER_VALUE, NEGATIVE_VALUE, FLOAT_VALUE, LARGE_FLOAT_VALUE, NEGATIVE_FLOAT_VALUE, LARGE_NEGATIVE_FLOAT_VALUE, STRING_VALUE, INSTANCE_VALUE)
VALUE_MAP = {
'd' : INT_VALUES,
'i' : INT_VALUES,
'o' : INT_VALUES,
'u' : INT_VALUES,
'x' : INT_VALUES,
'X' : INT_VALUES,
'e' : FLOAT_VALUES,
'E' : FLOAT_VALUES,
'f' : FLOAT_VALUES,
'F' : FLOAT_VALUES,
'g' : FLOAT_VALUES,
'G' : FLOAT_VALUES,
'c' : (INTEGER_VALUE,),
'r' : STRING_VALUES,
's' : STRING_VALUES,
}
while True:
s = get_str("Enter a %-format string:")
if not s:
break
f, conversions = PercentToBraceConverter(s).convert()
print('%s -> %s' % (s, f))
if conversions:
values = []
for c in conversions:
vals = VALUE_MAP.get(c.conversion, None)
if vals:
values.append(choice(vals))
values = tuple(values)
print("%-20s: %s" % ('Example raw values', ', '.join([repr(v) for v in values])))
print("%-20s: %s" % ('Output using %', s % values))
print("%-20s: %s" % ('Output using {}', f.format(*values)))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment