diff planemo/lib/python3.7/site-packages/libfuturize/fixer_util.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/planemo/lib/python3.7/site-packages/libfuturize/fixer_util.py	Fri Jul 31 00:32:28 2020 -0400
@@ -0,0 +1,520 @@
+"""
+Utility functions from 2to3, 3to2 and python-modernize (and some home-grown
+ones).
+
+Licences:
+2to3: PSF License v2
+3to2: Apache Software License (from 3to2/setup.py)
+python-modernize licence: BSD (from python-modernize/LICENSE)
+"""
+
+from lib2to3.fixer_util import (FromImport, Newline, is_import,
+                                find_root, does_tree_import, Comma)
+from lib2to3.pytree import Leaf, Node
+from lib2to3.pygram import python_symbols as syms, python_grammar
+from lib2to3.pygram import token
+from lib2to3.fixer_util import (Node, Call, Name, syms, Comma, Number)
+import re
+
+
+def canonical_fix_name(fix, avail_fixes):
+    """
+    Examples:
+    >>> canonical_fix_name('fix_wrap_text_literals')
+    'libfuturize.fixes.fix_wrap_text_literals'
+    >>> canonical_fix_name('wrap_text_literals')
+    'libfuturize.fixes.fix_wrap_text_literals'
+    >>> canonical_fix_name('wrap_te')
+    ValueError("unknown fixer name")
+    >>> canonical_fix_name('wrap')
+    ValueError("ambiguous fixer name")
+    """
+    if ".fix_" in fix:
+        return fix
+    else:
+        if fix.startswith('fix_'):
+            fix = fix[4:]
+        # Infer the full module name for the fixer.
+        # First ensure that no names clash (e.g.
+        # lib2to3.fixes.fix_blah and libfuturize.fixes.fix_blah):
+        found = [f for f in avail_fixes
+                 if f.endswith('fix_{0}'.format(fix))]
+        if len(found) > 1:
+            raise ValueError("Ambiguous fixer name. Choose a fully qualified "
+                  "module name instead from these:\n" +
+                  "\n".join("  " + myf for myf in found))
+        elif len(found) == 0:
+            raise ValueError("Unknown fixer. Use --list-fixes or -l for a list.")
+        return found[0]
+
+
+
+## These functions are from 3to2 by Joe Amenta:
+
+def Star(prefix=None):
+    return Leaf(token.STAR, u'*', prefix=prefix)
+
+def DoubleStar(prefix=None):
+    return Leaf(token.DOUBLESTAR, u'**', prefix=prefix)
+
+def Minus(prefix=None):
+    return Leaf(token.MINUS, u'-', prefix=prefix)
+
+def commatize(leafs):
+    """
+    Accepts/turns: (Name, Name, ..., Name, Name)
+    Returns/into: (Name, Comma, Name, Comma, ..., Name, Comma, Name)
+    """
+    new_leafs = []
+    for leaf in leafs:
+        new_leafs.append(leaf)
+        new_leafs.append(Comma())
+    del new_leafs[-1]
+    return new_leafs
+
+def indentation(node):
+    """
+    Returns the indentation for this node
+    Iff a node is in a suite, then it has indentation.
+    """
+    while node.parent is not None and node.parent.type != syms.suite:
+        node = node.parent
+    if node.parent is None:
+        return u""
+    # The first three children of a suite are NEWLINE, INDENT, (some other node)
+    # INDENT.value contains the indentation for this suite
+    # anything after (some other node) has the indentation as its prefix.
+    if node.type == token.INDENT:
+        return node.value
+    elif node.prev_sibling is not None and node.prev_sibling.type == token.INDENT:
+        return node.prev_sibling.value
+    elif node.prev_sibling is None:
+        return u""
+    else:
+        return node.prefix
+
+def indentation_step(node):
+    """
+    Dirty little trick to get the difference between each indentation level
+    Implemented by finding the shortest indentation string
+    (technically, the "least" of all of the indentation strings, but
+    tabs and spaces mixed won't get this far, so those are synonymous.)
+    """
+    r = find_root(node)
+    # Collect all indentations into one set.
+    all_indents = set(i.value for i in r.pre_order() if i.type == token.INDENT)
+    if not all_indents:
+        # nothing is indented anywhere, so we get to pick what we want
+        return u"    " # four spaces is a popular convention
+    else:
+        return min(all_indents)
+
+def suitify(parent):
+    """
+    Turn the stuff after the first colon in parent's children
+    into a suite, if it wasn't already
+    """
+    for node in parent.children:
+        if node.type == syms.suite:
+            # already in the prefered format, do nothing
+            return
+
+    # One-liners have no suite node, we have to fake one up
+    for i, node in enumerate(parent.children):
+        if node.type == token.COLON:
+            break
+    else:
+        raise ValueError(u"No class suite and no ':'!")
+    # Move everything into a suite node
+    suite = Node(syms.suite, [Newline(), Leaf(token.INDENT, indentation(node) + indentation_step(node))])
+    one_node = parent.children[i+1]
+    one_node.remove()
+    one_node.prefix = u''
+    suite.append_child(one_node)
+    parent.append_child(suite)
+
+def NameImport(package, as_name=None, prefix=None):
+    """
+    Accepts a package (Name node), name to import it as (string), and
+    optional prefix and returns a node:
+    import <package> [as <as_name>]
+    """
+    if prefix is None:
+        prefix = u""
+    children = [Name(u"import", prefix=prefix), package]
+    if as_name is not None:
+        children.extend([Name(u"as", prefix=u" "),
+                         Name(as_name, prefix=u" ")])
+    return Node(syms.import_name, children)
+
+_compound_stmts = (syms.if_stmt, syms.while_stmt, syms.for_stmt, syms.try_stmt, syms.with_stmt)
+_import_stmts = (syms.import_name, syms.import_from)
+
+def import_binding_scope(node):
+    """
+    Generator yields all nodes for which a node (an import_stmt) has scope
+    The purpose of this is for a call to _find() on each of them
+    """
+    # import_name / import_from are small_stmts
+    assert node.type in _import_stmts
+    test = node.next_sibling
+    # A small_stmt can only be followed by a SEMI or a NEWLINE.
+    while test.type == token.SEMI:
+        nxt = test.next_sibling
+        # A SEMI can only be followed by a small_stmt or a NEWLINE
+        if nxt.type == token.NEWLINE:
+            break
+        else:
+            yield nxt
+        # A small_stmt can only be followed by either a SEMI or a NEWLINE
+        test = nxt.next_sibling
+    # Covered all subsequent small_stmts after the import_stmt
+    # Now to cover all subsequent stmts after the parent simple_stmt
+    parent = node.parent
+    assert parent.type == syms.simple_stmt
+    test = parent.next_sibling
+    while test is not None:
+        # Yes, this will yield NEWLINE and DEDENT.  Deal with it.
+        yield test
+        test = test.next_sibling
+
+    context = parent.parent
+    # Recursively yield nodes following imports inside of a if/while/for/try/with statement
+    if context.type in _compound_stmts:
+        # import is in a one-liner
+        c = context
+        while c.next_sibling is not None:
+            yield c.next_sibling
+            c = c.next_sibling
+        context = context.parent
+
+    # Can't chain one-liners on one line, so that takes care of that.
+
+    p = context.parent
+    if p is None:
+        return
+
+    # in a multi-line suite
+
+    while p.type in _compound_stmts:
+
+        if context.type == syms.suite:
+            yield context
+
+        context = context.next_sibling
+
+        if context is None:
+            context = p.parent
+            p = context.parent
+            if p is None:
+                break
+
+def ImportAsName(name, as_name, prefix=None):
+    new_name = Name(name)
+    new_as = Name(u"as", prefix=u" ")
+    new_as_name = Name(as_name, prefix=u" ")
+    new_node = Node(syms.import_as_name, [new_name, new_as, new_as_name])
+    if prefix is not None:
+        new_node.prefix = prefix
+    return new_node
+
+
+def is_docstring(node):
+    """
+    Returns True if the node appears to be a docstring
+    """
+    return (node.type == syms.simple_stmt and
+            len(node.children) > 0 and node.children[0].type == token.STRING)
+
+
+def future_import(feature, node):
+    """
+    This seems to work
+    """
+    root = find_root(node)
+
+    if does_tree_import(u"__future__", feature, node):
+        return
+
+    # Look for a shebang or encoding line
+    shebang_encoding_idx = None
+
+    for idx, node in enumerate(root.children):
+        # Is it a shebang or encoding line?
+        if is_shebang_comment(node) or is_encoding_comment(node):
+            shebang_encoding_idx = idx
+        if is_docstring(node):
+            # skip over docstring
+            continue
+        names = check_future_import(node)
+        if not names:
+            # not a future statement; need to insert before this
+            break
+        if feature in names:
+            # already imported
+            return
+
+    import_ = FromImport(u'__future__', [Leaf(token.NAME, feature, prefix=" ")])
+    if shebang_encoding_idx == 0 and idx == 0:
+        # If this __future__ import would go on the first line,
+        # detach the shebang / encoding prefix from the current first line.
+        # and attach it to our new __future__ import node.
+        import_.prefix = root.children[0].prefix
+        root.children[0].prefix = u''
+        # End the __future__ import line with a newline and add a blank line
+        # afterwards:
+    children = [import_ , Newline()]
+    root.insert_child(idx, Node(syms.simple_stmt, children))
+
+
+def future_import2(feature, node):
+    """
+    An alternative to future_import() which might not work ...
+    """
+    root = find_root(node)
+
+    if does_tree_import(u"__future__", feature, node):
+        return
+
+    insert_pos = 0
+    for idx, node in enumerate(root.children):
+        if node.type == syms.simple_stmt and node.children and \
+           node.children[0].type == token.STRING:
+            insert_pos = idx + 1
+            break
+
+    for thing_after in root.children[insert_pos:]:
+        if thing_after.type == token.NEWLINE:
+            insert_pos += 1
+            continue
+
+        prefix = thing_after.prefix
+        thing_after.prefix = u""
+        break
+    else:
+        prefix = u""
+
+    import_ = FromImport(u"__future__", [Leaf(token.NAME, feature, prefix=u" ")])
+
+    children = [import_, Newline()]
+    root.insert_child(insert_pos, Node(syms.simple_stmt, children, prefix=prefix))
+
+def parse_args(arglist, scheme):
+    u"""
+    Parse a list of arguments into a dict
+    """
+    arglist = [i for i in arglist if i.type != token.COMMA]
+
+    ret_mapping = dict([(k, None) for k in scheme])
+
+    for i, arg in enumerate(arglist):
+        if arg.type == syms.argument and arg.children[1].type == token.EQUAL:
+            # argument < NAME '=' any >
+            slot = arg.children[0].value
+            ret_mapping[slot] = arg.children[2]
+        else:
+            slot = scheme[i]
+            ret_mapping[slot] = arg
+
+    return ret_mapping
+
+
+# def is_import_from(node):
+#     """Returns true if the node is a statement "from ... import ..."
+#     """
+#     return node.type == syms.import_from
+
+
+def is_import_stmt(node):
+    return (node.type == syms.simple_stmt and node.children and
+            is_import(node.children[0]))
+
+
+def touch_import_top(package, name_to_import, node):
+    """Works like `does_tree_import` but adds an import statement at the
+    top if it was not imported (but below any __future__ imports) and below any
+    comments such as shebang lines).
+
+    Based on lib2to3.fixer_util.touch_import()
+
+    Calling this multiple times adds the imports in reverse order.
+
+    Also adds "standard_library.install_aliases()" after "from future import
+    standard_library".  This should probably be factored into another function.
+    """
+
+    root = find_root(node)
+
+    if does_tree_import(package, name_to_import, root):
+        return
+
+    # Ideally, we would look for whether futurize --all-imports has been run,
+    # as indicated by the presence of ``from builtins import (ascii, ...,
+    # zip)`` -- and, if it has, we wouldn't import the name again.
+
+    # Look for __future__ imports and insert below them
+    found = False
+    for name in ['absolute_import', 'division', 'print_function',
+                 'unicode_literals']:
+        if does_tree_import('__future__', name, root):
+            found = True
+            break
+    if found:
+        # At least one __future__ import. We want to loop until we've seen them
+        # all.
+        start, end = None, None
+        for idx, node in enumerate(root.children):
+            if check_future_import(node):
+                start = idx
+                # Start looping
+                idx2 = start
+                while node:
+                    node = node.next_sibling
+                    idx2 += 1
+                    if not check_future_import(node):
+                        end = idx2
+                        break
+                break
+        assert start is not None
+        assert end is not None
+        insert_pos = end
+    else:
+        # No __future__ imports.
+        # We look for a docstring and insert the new node below that. If no docstring
+        # exists, just insert the node at the top.
+        for idx, node in enumerate(root.children):
+            if node.type != syms.simple_stmt:
+                break
+            if not is_docstring(node):
+                # This is the usual case.
+                break
+        insert_pos = idx
+
+    if package is None:
+        import_ = Node(syms.import_name, [
+            Leaf(token.NAME, u"import"),
+            Leaf(token.NAME, name_to_import, prefix=u" ")
+        ])
+    else:
+        import_ = FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")])
+        if name_to_import == u'standard_library':
+            # Add:
+            #     standard_library.install_aliases()
+            # after:
+            #     from future import standard_library
+            install_hooks = Node(syms.simple_stmt,
+                                 [Node(syms.power,
+                                       [Leaf(token.NAME, u'standard_library'),
+                                        Node(syms.trailer, [Leaf(token.DOT, u'.'),
+                                        Leaf(token.NAME, u'install_aliases')]),
+                                        Node(syms.trailer, [Leaf(token.LPAR, u'('),
+                                                            Leaf(token.RPAR, u')')])
+                                       ])
+                                 ]
+                                )
+            children_hooks = [install_hooks, Newline()]
+        else:
+            children_hooks = []
+
+        # FromImport(package, [Leaf(token.NAME, name_to_import, prefix=u" ")])
+
+    children_import = [import_, Newline()]
+    old_prefix = root.children[insert_pos].prefix
+    root.children[insert_pos].prefix = u''
+    root.insert_child(insert_pos, Node(syms.simple_stmt, children_import, prefix=old_prefix))
+    if len(children_hooks) > 0:
+        root.insert_child(insert_pos + 1, Node(syms.simple_stmt, children_hooks))
+
+
+## The following functions are from python-modernize by Armin Ronacher:
+# (a little edited).
+
+def check_future_import(node):
+    """If this is a future import, return set of symbols that are imported,
+    else return None."""
+    # node should be the import statement here
+    savenode = node
+    if not (node.type == syms.simple_stmt and node.children):
+        return set()
+    node = node.children[0]
+    # now node is the import_from node
+    if not (node.type == syms.import_from and
+            # node.type == token.NAME and      # seems to break it
+            hasattr(node.children[1], 'value') and
+            node.children[1].value == u'__future__'):
+        return set()
+    if node.children[3].type == token.LPAR:
+        node = node.children[4]
+    else:
+        node = node.children[3]
+    # now node is the import_as_name[s]
+    # print(python_grammar.number2symbol[node.type])  # breaks sometimes
+    if node.type == syms.import_as_names:
+        result = set()
+        for n in node.children:
+            if n.type == token.NAME:
+                result.add(n.value)
+            elif n.type == syms.import_as_name:
+                n = n.children[0]
+                assert n.type == token.NAME
+                result.add(n.value)
+        return result
+    elif node.type == syms.import_as_name:
+        node = node.children[0]
+        assert node.type == token.NAME
+        return set([node.value])
+    elif node.type == token.NAME:
+        return set([node.value])
+    else:
+        # TODO: handle brackets like this:
+        #     from __future__ import (absolute_import, division)
+        assert False, "strange import: %s" % savenode
+
+
+SHEBANG_REGEX = r'^#!.*python'
+ENCODING_REGEX = r"^#.*coding[:=]\s*([-\w.]+)"
+
+
+def is_shebang_comment(node):
+    """
+    Comments are prefixes for Leaf nodes. Returns whether the given node has a
+    prefix that looks like a shebang line or an encoding line:
+
+        #!/usr/bin/env python
+        #!/usr/bin/python3
+    """
+    return bool(re.match(SHEBANG_REGEX, node.prefix))
+
+
+def is_encoding_comment(node):
+    """
+    Comments are prefixes for Leaf nodes. Returns whether the given node has a
+    prefix that looks like an encoding line:
+
+        # coding: utf-8
+        # encoding: utf-8
+        # -*- coding: <encoding name> -*-
+        # vim: set fileencoding=<encoding name> :
+    """
+    return bool(re.match(ENCODING_REGEX, node.prefix))
+
+
+def wrap_in_fn_call(fn_name, args, prefix=None):
+    """
+    Example:
+    >>> wrap_in_fn_call("oldstr", (arg,))
+    oldstr(arg)
+
+    >>> wrap_in_fn_call("olddiv", (arg1, arg2))
+    olddiv(arg1, arg2)
+
+    >>> wrap_in_fn_call("olddiv", [arg1, comma, arg2, comma, arg3])
+    olddiv(arg1, arg2, arg3)
+    """
+    assert len(args) > 0
+    if len(args) == 2:
+        expr1, expr2 = args
+        newargs = [expr1, Comma(), expr2]
+    else:
+        newargs = args
+    return Call(Name(fn_name), newargs, prefix=prefix)