contrib/hgfixes/fix_bytesmod.py
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
Fri, 17 Oct 2014 02:07:05 +0900
changeset 22975 461342e1c8aa
parent 20701 d20817ac628a
permissions -rw-r--r--
import-checker: check modules for pure Python build correctly Before this patch, "import-checker.py" just replaces "/" in specified filenames by ".". This makes modules for pure Python build belong to "mercurial.pure" package, and prevents "import-checker.py" from correctly checking about cyclic dependency in them. This patch discards "pure" component from fully qualified name of such modules. To avoid discarding "pure" from the module name of standard libraries unexpectedly, this patch allows "dotted_name_of_path" to discard "pure" only from Mercurial specific modules, which are specified via command line arguments.

"""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):
        for bfn in blacklist:
            if self.filename.endswith(bfn):
                return
        if not self.filename.endswith('mercurial/py3kcompat.py'):
            touch_import('mercurial', '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