Index: example2366.py =================================================================== --- example2366.py (revision 0) +++ example2366.py (revision 0) @@ -0,0 +1,22 @@ +class X(obj,obj2): + x = 3 + __metaclass__ = Q + x = 7 + +class X(obj): + x = 3 + __metaclass__ = Q + x = 7 + +class X(): + x = 3 + __metaclass__ = Q + x = 7 + + +class X(): + __metaclass__=M + + + +# foo Index: lib2to3/fixes/fix_metaclass.py =================================================================== --- lib2to3/fixes/fix_metaclass.py (revision 0) +++ lib2to3/fixes/fix_metaclass.py (revision 0) @@ -0,0 +1,80 @@ +"""Fixer for __metaclass__ = X -> (metaclass=X) methods.""" +# Author: Jack Diederich + +# Local imports +from .import basefix +from .util import Name, syms, Node + + +""" lib2to3 stuff that I couldn't grok + * it was tough to normalize the arglist in the class definition + so this has two or three code paths to handle the different cases + * I create arbitrary nodes to hold other nodes. There is probably a + better way to do this. + * I clone nodes when I might not need to. + * BUG, if the final node (end of file) has the last char chopped off. + Name('passX') prints 'pass' +""" + +class FixMetaclass(basefix.BaseFix): + PATTERN = """ + classdef< 'class' any '(' (arglist|bases=any*) ')' ':' + suite< any* + meta_stmt=simple_stmt< + expr_stmt< '__metaclass__' '=' meta_rhs=any+ > rest=any+ + > ender=any* + > + > + + """ + def transform(self, nodes, results): + bases = results['bases'] + + # class X(base1, base2, ...) + if len(bases) > 1: + old_parent = bases[0].parent + parent = old_parent.clone() + parent.children = [] + new_bases = bases[:] + # class X() and class X(base) + else: + # create a placeholder before the ')' + parent = Node(259, []) # 259 is arbitrary, but it works + old_parent = Node(259, []) + new_bases = [] + + for i, node in enumerate(nodes.children): + if node.type == 8: # RPAREN + break + else: + raise Exception("Couldn't find right paren") + nodes.insert_child(i, old_parent) + + if bases: # class X(obj) + new_bases.append(Name(',')) + new_bases.append(Name(' ')) + new_bases.append(Name('metaclass')) + new_bases.append(Name('=')) + + meta_rhs = results['meta_rhs'] + meta_rhs[0].set_prefix('') + for ob in new_bases + meta_rhs: + ob_cp = ob.clone() + parent.append_child(ob_cp) + + # put our new code where the old bases list was + old_parent.replace(parent) + + # remove the old __metaclass__ line + meta_stmt = results['meta_stmt'] + meta_stmt.remove() + + # handle a class with just a __metaclass__ in the body + # (disabled because we can't garuantee that 'rest' is in the suite + rest = results['rest'] + str_rest = ''.join(str(node) for node in rest).strip(' \t\n') + if False and not str_rest: + results['rest'][0].parent.append_child(Name('passX', prefix='')) + + + return # DONE! Index: lib2to3/tests/test_fixers.py =================================================================== --- lib2to3/tests/test_fixers.py (revision 61654) +++ lib2to3/tests/test_fixers.py (working copy) @@ -3122,7 +3122,43 @@ a = "import .foo.bar as bang" self.check_both(b, a) +class Test_metaclass(FixerTestCase): + fixer = 'metaclass' + def test_nometa(self): + self.unchanged('class X(): pass') + self.unchanged('class X(object): pass') + self.unchanged('class X(object1, object2): pass') + + def test_onemeta(self): + old = 'class X(): __metaclass__ = Q\npass' + new = 'class X(metaclass=Q): pass' + self.check(old, new) + + old = 'class X(object): __metaclass__ = Q\npass' + new = 'class X(object, metaclass=Q): pass' + self.check(old, new) + + old = ''' +class X(object): + __metaclass__ = Meta + bar = 7 +''' + new = ''' +class X(object, metaclass=Meta): + bar = 7 +''' + self.check(old, new) + + old = ''' +class X(object): + bar = 7 + __metaclass__ = Meta +''' + self.check(old, new) + + + if __name__ == "__main__": import __main__ support.run_all_tests(__main__)