quick fix 2
This commit is contained in:
1
Lib/site-packages/libfuturize/__init__.py
Normal file
1
Lib/site-packages/libfuturize/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# empty to make this a package
|
||||
518
Lib/site-packages/libfuturize/fixer_util.py
Normal file
518
Lib/site-packages/libfuturize/fixer_util.py
Normal file
@ -0,0 +1,518 @@
|
||||
"""
|
||||
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,
|
||||
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)
|
||||
322
Lib/site-packages/libfuturize/main.py
Normal file
322
Lib/site-packages/libfuturize/main.py
Normal file
@ -0,0 +1,322 @@
|
||||
"""
|
||||
futurize: automatic conversion to clean 2/3 code using ``python-future``
|
||||
======================================================================
|
||||
|
||||
Like Armin Ronacher's modernize.py, ``futurize`` attempts to produce clean
|
||||
standard Python 3 code that runs on both Py2 and Py3.
|
||||
|
||||
One pass
|
||||
--------
|
||||
|
||||
Use it like this on Python 2 code:
|
||||
|
||||
$ futurize --verbose mypython2script.py
|
||||
|
||||
This will attempt to port the code to standard Py3 code that also
|
||||
provides Py2 compatibility with the help of the right imports from
|
||||
``future``.
|
||||
|
||||
To write changes to the files, use the -w flag.
|
||||
|
||||
Two stages
|
||||
----------
|
||||
|
||||
The ``futurize`` script can also be called in two separate stages. First:
|
||||
|
||||
$ futurize --stage1 mypython2script.py
|
||||
|
||||
This produces more modern Python 2 code that is not yet compatible with Python
|
||||
3. The tests should still run and the diff should be uncontroversial to apply to
|
||||
most Python projects that are willing to drop support for Python 2.5 and lower.
|
||||
|
||||
After this, the recommended approach is to explicitly mark all strings that must
|
||||
be byte-strings with a b'' prefix and all text (unicode) strings with a u''
|
||||
prefix, and then invoke the second stage of Python 2 to 2/3 conversion with::
|
||||
|
||||
$ futurize --stage2 mypython2script.py
|
||||
|
||||
Stage 2 adds a dependency on ``future``. It converts most remaining Python
|
||||
2-specific code to Python 3 code and adds appropriate imports from ``future``
|
||||
to restore Py2 support.
|
||||
|
||||
The command above leaves all unadorned string literals as native strings
|
||||
(byte-strings on Py2, unicode strings on Py3). If instead you would like all
|
||||
unadorned string literals to be promoted to unicode, you can also pass this
|
||||
flag:
|
||||
|
||||
$ futurize --stage2 --unicode-literals mypython2script.py
|
||||
|
||||
This adds the declaration ``from __future__ import unicode_literals`` to the
|
||||
top of each file, which implicitly declares all unadorned string literals to be
|
||||
unicode strings (``unicode`` on Py2).
|
||||
|
||||
All imports
|
||||
-----------
|
||||
|
||||
The --all-imports option forces adding all ``__future__`` imports,
|
||||
``builtins`` imports, and standard library aliases, even if they don't
|
||||
seem necessary for the current state of each module. (This can simplify
|
||||
testing, and can reduce the need to think about Py2 compatibility when editing
|
||||
the code further.)
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import (absolute_import, print_function, unicode_literals)
|
||||
import future.utils
|
||||
from future import __version__
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
|
||||
from lib2to3.main import warn, StdoutRefactoringTool
|
||||
from lib2to3 import refactor
|
||||
|
||||
from libfuturize.fixes import (lib2to3_fix_names_stage1,
|
||||
lib2to3_fix_names_stage2,
|
||||
libfuturize_fix_names_stage1,
|
||||
libfuturize_fix_names_stage2)
|
||||
|
||||
fixer_pkg = 'libfuturize.fixes'
|
||||
|
||||
|
||||
def main(args=None):
|
||||
"""Main program.
|
||||
|
||||
Args:
|
||||
fixer_pkg: the name of a package where the fixers are located.
|
||||
args: optional; a list of command line arguments. If omitted,
|
||||
sys.argv[1:] is used.
|
||||
|
||||
Returns a suggested exit status (0, 1, 2).
|
||||
"""
|
||||
|
||||
# Set up option parser
|
||||
parser = optparse.OptionParser(usage="futurize [options] file|dir ...")
|
||||
parser.add_option("-V", "--version", action="store_true",
|
||||
help="Report the version number of futurize")
|
||||
parser.add_option("-a", "--all-imports", action="store_true",
|
||||
help="Add all __future__ and future imports to each module")
|
||||
parser.add_option("-1", "--stage1", action="store_true",
|
||||
help="Modernize Python 2 code only; no compatibility with Python 3 (or dependency on ``future``)")
|
||||
parser.add_option("-2", "--stage2", action="store_true",
|
||||
help="Take modernized (stage1) code and add a dependency on ``future`` to provide Py3 compatibility.")
|
||||
parser.add_option("-0", "--both-stages", action="store_true",
|
||||
help="Apply both stages 1 and 2")
|
||||
parser.add_option("-u", "--unicode-literals", action="store_true",
|
||||
help="Add ``from __future__ import unicode_literals`` to implicitly convert all unadorned string literals '' into unicode strings")
|
||||
parser.add_option("-f", "--fix", action="append", default=[],
|
||||
help="Each FIX specifies a transformation; default: all.\nEither use '-f division -f metaclass' etc. or use the fully-qualified module name: '-f lib2to3.fixes.fix_types -f libfuturize.fixes.fix_unicode_keep_u'")
|
||||
parser.add_option("-j", "--processes", action="store", default=1,
|
||||
type="int", help="Run 2to3 concurrently")
|
||||
parser.add_option("-x", "--nofix", action="append", default=[],
|
||||
help="Prevent a fixer from being run.")
|
||||
parser.add_option("-l", "--list-fixes", action="store_true",
|
||||
help="List available transformations")
|
||||
parser.add_option("-p", "--print-function", action="store_true",
|
||||
help="Modify the grammar so that print() is a function")
|
||||
parser.add_option("-v", "--verbose", action="store_true",
|
||||
help="More verbose logging")
|
||||
parser.add_option("--no-diffs", action="store_true",
|
||||
help="Don't show diffs of the refactoring")
|
||||
parser.add_option("-w", "--write", action="store_true",
|
||||
help="Write back modified files")
|
||||
parser.add_option("-n", "--nobackups", action="store_true", default=False,
|
||||
help="Don't write backups for modified files.")
|
||||
parser.add_option("-o", "--output-dir", action="store", type="str",
|
||||
default="", help="Put output files in this directory "
|
||||
"instead of overwriting the input files. Requires -n. "
|
||||
"For Python >= 2.7 only.")
|
||||
parser.add_option("-W", "--write-unchanged-files", action="store_true",
|
||||
help="Also write files even if no changes were required"
|
||||
" (useful with --output-dir); implies -w.")
|
||||
parser.add_option("--add-suffix", action="store", type="str", default="",
|
||||
help="Append this string to all output filenames."
|
||||
" Requires -n if non-empty. For Python >= 2.7 only."
|
||||
"ex: --add-suffix='3' will generate .py3 files.")
|
||||
|
||||
# Parse command line arguments
|
||||
flags = {}
|
||||
refactor_stdin = False
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
if options.write_unchanged_files:
|
||||
flags["write_unchanged_files"] = True
|
||||
if not options.write:
|
||||
warn("--write-unchanged-files/-W implies -w.")
|
||||
options.write = True
|
||||
# If we allowed these, the original files would be renamed to backup names
|
||||
# but not replaced.
|
||||
if options.output_dir and not options.nobackups:
|
||||
parser.error("Can't use --output-dir/-o without -n.")
|
||||
if options.add_suffix and not options.nobackups:
|
||||
parser.error("Can't use --add-suffix without -n.")
|
||||
|
||||
if not options.write and options.no_diffs:
|
||||
warn("not writing files and not printing diffs; that's not very useful")
|
||||
if not options.write and options.nobackups:
|
||||
parser.error("Can't use -n without -w")
|
||||
if "-" in args:
|
||||
refactor_stdin = True
|
||||
if options.write:
|
||||
print("Can't write to stdin.", file=sys.stderr)
|
||||
return 2
|
||||
# Is this ever necessary?
|
||||
if options.print_function:
|
||||
flags["print_function"] = True
|
||||
|
||||
# Set up logging handler
|
||||
level = logging.DEBUG if options.verbose else logging.INFO
|
||||
logging.basicConfig(format='%(name)s: %(message)s', level=level)
|
||||
logger = logging.getLogger('libfuturize.main')
|
||||
|
||||
if options.stage1 or options.stage2:
|
||||
assert options.both_stages is None
|
||||
options.both_stages = False
|
||||
else:
|
||||
options.both_stages = True
|
||||
|
||||
avail_fixes = set()
|
||||
|
||||
if options.stage1 or options.both_stages:
|
||||
avail_fixes.update(lib2to3_fix_names_stage1)
|
||||
avail_fixes.update(libfuturize_fix_names_stage1)
|
||||
if options.stage2 or options.both_stages:
|
||||
avail_fixes.update(lib2to3_fix_names_stage2)
|
||||
avail_fixes.update(libfuturize_fix_names_stage2)
|
||||
|
||||
if options.unicode_literals:
|
||||
avail_fixes.add('libfuturize.fixes.fix_unicode_literals_import')
|
||||
|
||||
if options.version:
|
||||
print(__version__)
|
||||
return 0
|
||||
if options.list_fixes:
|
||||
print("Available transformations for the -f/--fix option:")
|
||||
# for fixname in sorted(refactor.get_all_fix_names(fixer_pkg)):
|
||||
for fixname in sorted(avail_fixes):
|
||||
print(fixname)
|
||||
if not args:
|
||||
return 0
|
||||
if not args:
|
||||
print("At least one file or directory argument required.",
|
||||
file=sys.stderr)
|
||||
print("Use --help to show usage.", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
unwanted_fixes = set()
|
||||
for fix in options.nofix:
|
||||
if ".fix_" in fix:
|
||||
unwanted_fixes.add(fix)
|
||||
else:
|
||||
# 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:
|
||||
print("Ambiguous fixer name. Choose a fully qualified "
|
||||
"module name instead from these:\n" +
|
||||
"\n".join(" " + myf for myf in found),
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
elif len(found) == 0:
|
||||
print("Unknown fixer. Use --list-fixes or -l for a list.",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
unwanted_fixes.add(found[0])
|
||||
|
||||
extra_fixes = set()
|
||||
if options.all_imports:
|
||||
if options.stage1:
|
||||
prefix = 'libfuturize.fixes.'
|
||||
extra_fixes.add(prefix +
|
||||
'fix_add__future__imports_except_unicode_literals')
|
||||
else:
|
||||
# In case the user hasn't run stage1 for some reason:
|
||||
prefix = 'libpasteurize.fixes.'
|
||||
extra_fixes.add(prefix + 'fix_add_all__future__imports')
|
||||
extra_fixes.add(prefix + 'fix_add_future_standard_library_import')
|
||||
extra_fixes.add(prefix + 'fix_add_all_future_builtins')
|
||||
explicit = set()
|
||||
if options.fix:
|
||||
all_present = False
|
||||
for fix in options.fix:
|
||||
if fix == 'all':
|
||||
all_present = True
|
||||
else:
|
||||
if ".fix_" in fix:
|
||||
explicit.add(fix)
|
||||
else:
|
||||
# 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:
|
||||
print("Ambiguous fixer name. Choose a fully qualified "
|
||||
"module name instead from these:\n" +
|
||||
"\n".join(" " + myf for myf in found),
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
elif len(found) == 0:
|
||||
print("Unknown fixer. Use --list-fixes or -l for a list.",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
explicit.add(found[0])
|
||||
if len(explicit & unwanted_fixes) > 0:
|
||||
print("Conflicting usage: the following fixers have been "
|
||||
"simultaneously requested and disallowed:\n" +
|
||||
"\n".join(" " + myf for myf in (explicit & unwanted_fixes)),
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
requested = avail_fixes.union(explicit) if all_present else explicit
|
||||
else:
|
||||
requested = avail_fixes.union(explicit)
|
||||
fixer_names = (requested | extra_fixes) - unwanted_fixes
|
||||
|
||||
input_base_dir = os.path.commonprefix(args)
|
||||
if (input_base_dir and not input_base_dir.endswith(os.sep)
|
||||
and not os.path.isdir(input_base_dir)):
|
||||
# One or more similar names were passed, their directory is the base.
|
||||
# os.path.commonprefix() is ignorant of path elements, this corrects
|
||||
# for that weird API.
|
||||
input_base_dir = os.path.dirname(input_base_dir)
|
||||
if options.output_dir:
|
||||
input_base_dir = input_base_dir.rstrip(os.sep)
|
||||
logger.info('Output in %r will mirror the input directory %r layout.',
|
||||
options.output_dir, input_base_dir)
|
||||
|
||||
# Initialize the refactoring tool
|
||||
if future.utils.PY26:
|
||||
extra_kwargs = {}
|
||||
else:
|
||||
extra_kwargs = {
|
||||
'append_suffix': options.add_suffix,
|
||||
'output_dir': options.output_dir,
|
||||
'input_base_dir': input_base_dir,
|
||||
}
|
||||
|
||||
rt = StdoutRefactoringTool(
|
||||
sorted(fixer_names), flags, sorted(explicit),
|
||||
options.nobackups, not options.no_diffs,
|
||||
**extra_kwargs)
|
||||
|
||||
# Refactor all files and directories passed as arguments
|
||||
if not rt.errors:
|
||||
if refactor_stdin:
|
||||
rt.refactor_stdin()
|
||||
else:
|
||||
try:
|
||||
rt.refactor(args, options.write, None,
|
||||
options.processes)
|
||||
except refactor.MultiprocessingUnsupported:
|
||||
assert options.processes > 1
|
||||
print("Sorry, -j isn't " \
|
||||
"supported on this platform.", file=sys.stderr)
|
||||
return 1
|
||||
rt.summarize()
|
||||
|
||||
# Return error status (0 if rt.errors is zero)
|
||||
return int(bool(rt.errors))
|
||||
Reference in New Issue
Block a user