Created
October 3, 2009 22:25
-
-
Save vsajip/200936 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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