# HG changeset patch # User Renato Cunha # Date 2010-08-03 16:59:14 # Node ID e627fef946043495d3f0567fa48ea8d386495814 # Parent 37a70a78439773325b761d30af723e479056ae6e hgfixes: added a fixer that makes bytes to be formatted correctly This patch implement a fixer that replaces all calls to the '%' when bytes arguments are used to a call to bytesformatter(), a function that knows how to format byte strings. As one can't be sure if a formatting call is done when only variables are used in a '%' call, these calls are also translated. The bytesformatter, in runtime, makes sure to return the "raw" % operation if that's what was intended. diff --git a/contrib/hgfixes/fix_bytesmod.py b/contrib/hgfixes/fix_bytesmod.py new file mode 100644 --- /dev/null +++ b/contrib/hgfixes/fix_bytesmod.py @@ -0,0 +1,63 @@ +"""Fixer that changes bytes % whatever to a function that actually formats +it.""" + +from lib2to3 import fixer_base +from lib2to3.fixer_util import is_tuple, Call, Comma, Name, touch_import + +# XXX: Implementing a blacklist in 2to3 turned out to be more troublesome than +# blacklisting some modules inside the fixers. So, this is what I came with. + +blacklist = ['mercurial/demandimport.py', + 'mercurial/py3kcompat.py', + 'mercurial/i18n.py', + ] + +def isnumberremainder(formatstr, data): + try: + if data.value.isdigit(): + return True + except AttributeError: + return False + +class FixBytesmod(fixer_base.BaseFix): + # XXX: There's one case (I suppose) I can't handle: when a remainder + # operation like foo % bar is performed, I can't really know what the + # contents of foo and bar are. I believe the best approach is to "correct" + # the to-be-converted code and let bytesformatter handle that case in + # runtime. + PATTERN = ''' + term< formatstr=STRING '%' data=STRING > | + term< formatstr=STRING '%' data=atom > | + term< formatstr=NAME '%' data=any > | + term< formatstr=any '%' data=any > + ''' + + def transform(self, node, results): + if self.filename in blacklist: + return + elif self.filename == 'mercurial/util.py': + touch_import('.', 'py3kcompat', node=node) + + formatstr = results['formatstr'].clone() + data = results['data'].clone() + formatstr.prefix = '' # remove spaces from start + + if isnumberremainder(formatstr, data): + return + + # We have two possibilities: + # 1- An identifier or name is passed, it is going to be a leaf, thus, we + # just need to copy its value as an argument to the formatter; + # 2- A tuple is explicitly passed. In this case, we're gonna explode it + # to pass to the formatter + # TODO: Check for normal strings. They don't need to be translated + + if is_tuple(data): + args = [formatstr, Comma().clone()] + \ + [c.clone() for c in data.children[:]] + else: + args = [formatstr, Comma().clone(), data] + + call = Call(Name('bytesformatter', prefix = ' '), args) + return call +