"""
Utility functions from 2 to3 , 3 to2 and python - modernize ( and some home - grown
ones ) .
Licences :
2 to3 : PSF License v2
3 to2 : Apache Software License ( from 3 to2 / setup . py )
python - modernize licence : BSD ( from python - modernize / LICENSE )
"""
from lib2to3 . fixer_util import ( FromImport , Newline , is_import ,
find_root , does_tree_import ,
Call , Name , Comma )
from lib2to3 . pytree import Leaf , Node
from lib2to3 . pygram import python_symbols as syms
from lib2to3 . pygram import token
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 preferred 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
children_hooks = [ ]
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 ( ) ]
# 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]
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 )