123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- """
- 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)
|