init
This commit is contained in:
commit
38355d2442
9083 changed files with 1225834 additions and 0 deletions
515
.venv/lib/python3.8/site-packages/pyflyby/_cmdline.py
Normal file
515
.venv/lib/python3.8/site-packages/pyflyby/_cmdline.py
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
# pyflyby/_cmdline.py.
|
||||
# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen.
|
||||
# License: MIT http://opensource.org/licenses/MIT
|
||||
|
||||
from __future__ import (absolute_import, division, print_function,
|
||||
with_statement)
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import six
|
||||
from six import reraise
|
||||
from six.moves import input
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
import traceback
|
||||
|
||||
|
||||
from pyflyby._file import (FileText, Filename, atomic_write_file,
|
||||
expand_py_files_from_args, read_file)
|
||||
from pyflyby._importstmt import ImportFormatParams
|
||||
from pyflyby._log import logger
|
||||
from pyflyby._util import cached_attribute, indent
|
||||
|
||||
|
||||
def hfmt(s):
|
||||
return dedent(s).strip()
|
||||
|
||||
def maindoc():
|
||||
import __main__
|
||||
return (__main__.__doc__ or '').strip()
|
||||
|
||||
|
||||
def _sigpipe_handler(*args):
|
||||
# The parent process piped our stdout and closed the pipe before we
|
||||
# finished writing, e.g. "tidy-imports ... | head" or "tidy-imports ... |
|
||||
# less". Exit quietly - squelch the "close failed in file object
|
||||
# destructor" message would otherwise be raised.
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def parse_args(addopts=None, import_format_params=False, modify_action_params=False):
|
||||
"""
|
||||
Do setup for a top-level script and parse arguments.
|
||||
"""
|
||||
### Setup.
|
||||
# Register a SIGPIPE handler.
|
||||
signal.signal(signal.SIGPIPE, _sigpipe_handler)
|
||||
### Parse args.
|
||||
parser = optparse.OptionParser(usage='\n'+maindoc())
|
||||
|
||||
def log_level_callbacker(level):
|
||||
def callback(option, opt_str, value, parser):
|
||||
logger.set_level(level)
|
||||
return callback
|
||||
|
||||
def debug_callback(option, opt_str, value, parser):
|
||||
logger.set_level("DEBUG")
|
||||
|
||||
parser.add_option("--debug", action="callback",
|
||||
callback=debug_callback,
|
||||
help="Debug mode (noisy and fail fast).")
|
||||
|
||||
parser.add_option("--verbose", action="callback",
|
||||
callback=log_level_callbacker("DEBUG"),
|
||||
help="Be noisy.")
|
||||
|
||||
parser.add_option("--quiet", action="callback",
|
||||
callback=log_level_callbacker("ERROR"),
|
||||
help="Be quiet.")
|
||||
|
||||
parser.add_option("--version", action="callback",
|
||||
callback=lambda *args: print_version_and_exit(),
|
||||
help="Print pyflyby version and exit.")
|
||||
|
||||
if modify_action_params:
|
||||
group = optparse.OptionGroup(parser, "Action options")
|
||||
action_diff = action_external_command('pyflyby-diff')
|
||||
def parse_action(v):
|
||||
V = v.strip().upper()
|
||||
if V == 'PRINT':
|
||||
return action_print
|
||||
elif V == 'REPLACE':
|
||||
return action_replace
|
||||
elif V == 'QUERY':
|
||||
return action_query()
|
||||
elif V == "DIFF":
|
||||
return action_diff
|
||||
elif V.startswith("QUERY:"):
|
||||
return action_query(v[6:])
|
||||
elif V.startswith("EXECUTE:"):
|
||||
return action_external_command(v[8:])
|
||||
elif V == "IFCHANGED":
|
||||
return action_ifchanged
|
||||
else:
|
||||
raise Exception(
|
||||
"Bad argument %r to --action; "
|
||||
"expected PRINT or REPLACE or QUERY or IFCHANGED "
|
||||
"or EXECUTE:..." % (v,))
|
||||
|
||||
def set_actions(actions):
|
||||
actions = tuple(actions)
|
||||
parser.values.actions = actions
|
||||
|
||||
def action_callback(option, opt_str, value, parser):
|
||||
action_args = value.split(',')
|
||||
set_actions([parse_action(v) for v in action_args])
|
||||
|
||||
def action_callbacker(actions):
|
||||
def callback(option, opt_str, value, parser):
|
||||
set_actions(actions)
|
||||
return callback
|
||||
|
||||
group.add_option(
|
||||
"--actions", type='string', action='callback',
|
||||
callback=action_callback,
|
||||
metavar='PRINT|REPLACE|IFCHANGED|QUERY|DIFF|EXECUTE:mycommand',
|
||||
help=hfmt('''
|
||||
Comma-separated list of action(s) to take. If PRINT, print
|
||||
the changed file to stdout. If REPLACE, then modify the
|
||||
file in-place. If EXECUTE:mycommand, then execute
|
||||
'mycommand oldfile tmpfile'. If DIFF, then execute
|
||||
'pyflyby-diff'. If QUERY, then query user to continue.
|
||||
If IFCHANGED, then continue actions only if file was
|
||||
changed.'''))
|
||||
group.add_option(
|
||||
"--print", "-p", action='callback',
|
||||
callback=action_callbacker([action_print]),
|
||||
help=hfmt('''
|
||||
Equivalent to --action=PRINT (default when stdin or stdout is
|
||||
not a tty) '''))
|
||||
group.add_option(
|
||||
"--diff", "-d", action='callback',
|
||||
callback=action_callbacker([action_diff]),
|
||||
help=hfmt('''Equivalent to --action=DIFF'''))
|
||||
group.add_option(
|
||||
"--replace", "-r", action='callback',
|
||||
callback=action_callbacker([action_ifchanged, action_replace]),
|
||||
help=hfmt('''Equivalent to --action=IFCHANGED,REPLACE'''))
|
||||
group.add_option(
|
||||
"--diff-replace", "-R", action='callback',
|
||||
callback=action_callbacker([action_ifchanged, action_diff, action_replace]),
|
||||
help=hfmt('''Equivalent to --action=IFCHANGED,DIFF,REPLACE'''))
|
||||
actions_interactive = [
|
||||
action_ifchanged, action_diff,
|
||||
action_query("Replace {filename}?"), action_replace]
|
||||
group.add_option(
|
||||
"--interactive", "-i", action='callback',
|
||||
callback=action_callbacker(actions_interactive),
|
||||
help=hfmt('''
|
||||
Equivalent to --action=IFCHANGED,DIFF,QUERY,REPLACE (default
|
||||
when stdin & stdout are ttys) '''))
|
||||
if os.isatty(0) and os.isatty(1):
|
||||
default_actions = actions_interactive
|
||||
else:
|
||||
default_actions = [action_print]
|
||||
parser.set_default('actions', tuple(default_actions))
|
||||
parser.add_option_group(group)
|
||||
|
||||
parser.add_option(
|
||||
'--symlinks', action='callback', nargs=1, type=str,
|
||||
dest='symlinks', callback=symlink_callback, help="--symlinks should be one of: " + symlinks_help,
|
||||
)
|
||||
parser.set_defaults(symlinks='error')
|
||||
|
||||
if import_format_params:
|
||||
group = optparse.OptionGroup(parser, "Pretty-printing options")
|
||||
group.add_option('--align-imports', '--align', type='str', default="32",
|
||||
metavar='N',
|
||||
help=hfmt('''
|
||||
Whether and how to align the 'import' keyword in
|
||||
'from modulename import aliases...'. If 0, then
|
||||
don't align. If 1, then align within each block
|
||||
of imports. If an integer > 1, then align at
|
||||
that column, wrapping with a backslash if
|
||||
necessary. If a comma-separated list of integers
|
||||
(tab stops), then pick the column that results in
|
||||
the fewest number of lines total per block.'''))
|
||||
group.add_option('--from-spaces', type='int', default=3, metavar='N',
|
||||
help=hfmt('''
|
||||
The number of spaces after the 'from' keyword.
|
||||
(Must be at least 1; default is 3.)'''))
|
||||
group.add_option('--separate-from-imports', action='store_true',
|
||||
default=False,
|
||||
help=hfmt('''
|
||||
Separate 'from ... import ...'
|
||||
statements from 'import ...' statements.'''))
|
||||
group.add_option('--no-separate-from-imports', action='store_false',
|
||||
dest='separate_from_imports',
|
||||
help=hfmt('''
|
||||
(Default) Don't separate 'from ... import ...'
|
||||
statements from 'import ...' statements.'''))
|
||||
group.add_option('--align-future', action='store_true',
|
||||
default=False,
|
||||
help=hfmt('''
|
||||
Align the 'from __future__ import ...' statement
|
||||
like others.'''))
|
||||
group.add_option('--no-align-future', action='store_false',
|
||||
dest='align_future',
|
||||
help=hfmt('''
|
||||
(Default) Don't align the 'from __future__ import
|
||||
...' statement.'''))
|
||||
group.add_option('--width', type='int', default=79, metavar='N',
|
||||
help=hfmt('''
|
||||
Maximum line length (default: 79).'''))
|
||||
group.add_option('--black', action='store_true', default=False,
|
||||
help=hfmt('''
|
||||
Use black to format imports. If this option is
|
||||
used, all other formatting options are ignored.'''))
|
||||
group.add_option('--hanging-indent', type='choice', default='never',
|
||||
choices=['never','auto','always'],
|
||||
metavar='never|auto|always',
|
||||
dest='hanging_indent',
|
||||
help=hfmt('''
|
||||
How to wrap import statements that don't fit on
|
||||
one line.
|
||||
If --hanging-indent=always, then always indent
|
||||
imported tokens at column 4 on the next line.
|
||||
If --hanging-indent=never (default), then align
|
||||
import tokens after "import (" (by default column
|
||||
40); do so even if some symbols are so long that
|
||||
this would exceed the width (by default 79)).
|
||||
If --hanging-indent=auto, then use hanging indent
|
||||
only if it is necessary to prevent exceeding the
|
||||
width (by default 79).
|
||||
'''))
|
||||
def uniform_callback(option, opt_str, value, parser):
|
||||
parser.values.separate_from_imports = False
|
||||
parser.values.from_spaces = 3
|
||||
parser.values.align_imports = '32'
|
||||
group.add_option('--uniform', '-u', action="callback",
|
||||
callback=uniform_callback,
|
||||
help=hfmt('''
|
||||
(Default) Shortcut for --no-separate-from-imports
|
||||
--from-spaces=3 --align-imports=32.'''))
|
||||
def unaligned_callback(option, opt_str, value, parser):
|
||||
parser.values.separate_from_imports = True
|
||||
parser.values.from_spaces = 1
|
||||
parser.values.align_imports = '0'
|
||||
group.add_option('--unaligned', '-n', action="callback",
|
||||
callback=unaligned_callback,
|
||||
help=hfmt('''
|
||||
Shortcut for --separate-from-imports
|
||||
--from-spaces=1 --align-imports=0.'''))
|
||||
|
||||
parser.add_option_group(group)
|
||||
if addopts is not None:
|
||||
addopts(parser)
|
||||
# This is the only way to provide a default value for an option with a
|
||||
# callback.
|
||||
if modify_action_params:
|
||||
args = ["--symlinks=error"] + sys.argv[1:]
|
||||
else:
|
||||
args = None
|
||||
options, args = parser.parse_args(args=args)
|
||||
if import_format_params:
|
||||
align_imports_args = [int(x.strip())
|
||||
for x in options.align_imports.split(",")]
|
||||
if len(align_imports_args) == 1 and align_imports_args[0] == 1:
|
||||
align_imports = True
|
||||
elif len(align_imports_args) == 1 and align_imports_args[0] == 0:
|
||||
align_imports = False
|
||||
else:
|
||||
align_imports = tuple(sorted(set(align_imports_args)))
|
||||
options.params = ImportFormatParams(
|
||||
align_imports =align_imports,
|
||||
from_spaces =options.from_spaces,
|
||||
separate_from_imports =options.separate_from_imports,
|
||||
max_line_length =options.width,
|
||||
use_black =options.black,
|
||||
align_future =options.align_future,
|
||||
hanging_indent =options.hanging_indent,
|
||||
)
|
||||
return options, args
|
||||
|
||||
|
||||
def _default_on_error(filename):
|
||||
raise SystemExit("bad filename %s" % (filename,))
|
||||
|
||||
def filename_args(args, on_error=_default_on_error):
|
||||
"""
|
||||
Return list of filenames given command-line arguments.
|
||||
|
||||
:rtype:
|
||||
``list`` of `Filename`
|
||||
"""
|
||||
if args:
|
||||
return expand_py_files_from_args(args, on_error)
|
||||
elif not os.isatty(0):
|
||||
return [Filename.STDIN]
|
||||
else:
|
||||
syntax()
|
||||
|
||||
|
||||
def print_version_and_exit(extra=None):
|
||||
from pyflyby._version import __version__
|
||||
msg = "pyflyby %s" % (__version__,)
|
||||
progname = os.path.realpath(sys.argv[0])
|
||||
if os.path.exists(progname):
|
||||
msg += " (%s)" % (os.path.basename(progname),)
|
||||
print(msg)
|
||||
if extra:
|
||||
print(extra)
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def syntax(message=None, usage=None):
|
||||
if message:
|
||||
logger.error(message)
|
||||
outmsg = ((usage or maindoc()) +
|
||||
'\n\nFor usage, see: %s --help' % (sys.argv[0],))
|
||||
print(outmsg, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
class AbortActions(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Modifier(object):
|
||||
def __init__(self, modifier, filename):
|
||||
self.modifier = modifier
|
||||
self.filename = filename
|
||||
self._tmpfiles = []
|
||||
|
||||
@cached_attribute
|
||||
def input_content(self):
|
||||
return read_file(self.filename)
|
||||
|
||||
# TODO: refactor to avoid having these heavy-weight things inside a
|
||||
# cached_attribute, which causes annoyance while debugging.
|
||||
@cached_attribute
|
||||
def output_content(self):
|
||||
return FileText(self.modifier(self.input_content), filename=self.filename)
|
||||
|
||||
def _tempfile(self):
|
||||
from tempfile import NamedTemporaryFile
|
||||
f = NamedTemporaryFile()
|
||||
self._tmpfiles.append(f)
|
||||
return f, Filename(f.name)
|
||||
|
||||
|
||||
@cached_attribute
|
||||
def output_content_filename(self):
|
||||
f, fname = self._tempfile()
|
||||
if six.PY3:
|
||||
f.write(bytes(self.output_content.joined, "utf-8"))
|
||||
else:
|
||||
f.write(self.output_content.joined.encode('utf-8'))
|
||||
f.flush()
|
||||
return fname
|
||||
|
||||
@cached_attribute
|
||||
def input_content_filename(self):
|
||||
if isinstance(self.filename, Filename):
|
||||
return self.filename
|
||||
# If the input was stdin, and the user wants a diff, then we need to
|
||||
# write it to a temp file.
|
||||
f, fname = self._tempfile()
|
||||
if six.PY3:
|
||||
f.write(bytes(self.input_content, "utf-8"))
|
||||
else:
|
||||
f.write(self.input_content)
|
||||
f.flush()
|
||||
return fname
|
||||
|
||||
|
||||
def __del__(self):
|
||||
for f in self._tmpfiles:
|
||||
f.close()
|
||||
|
||||
|
||||
def process_actions(filenames, actions, modify_function,
|
||||
reraise_exceptions=()):
|
||||
errors = []
|
||||
def on_error_filename_arg(arg):
|
||||
print("%s: bad filename %s" % (sys.argv[0], arg), file=sys.stderr)
|
||||
errors.append("%s: bad filename" % (arg,))
|
||||
filenames = filename_args(filenames, on_error=on_error_filename_arg)
|
||||
for filename in filenames:
|
||||
try:
|
||||
m = Modifier(modify_function, filename)
|
||||
for action in actions:
|
||||
action(m)
|
||||
except AbortActions:
|
||||
continue
|
||||
except reraise_exceptions:
|
||||
raise
|
||||
except Exception as e:
|
||||
errors.append("%s: %s: %s" % (filename, type(e).__name__, e))
|
||||
type_e = type(e)
|
||||
try:
|
||||
tb = sys.exc_info()[2]
|
||||
if str(filename) not in str(e):
|
||||
try:
|
||||
e = type_e("While processing %s: %s" % (filename, e))
|
||||
pass
|
||||
except TypeError:
|
||||
# Exception takes more than one argument
|
||||
pass
|
||||
if logger.debug_enabled:
|
||||
reraise(type_e, e, tb)
|
||||
traceback.print_exception(type(e), e, tb)
|
||||
finally:
|
||||
tb = None # avoid refcycles involving tb
|
||||
continue
|
||||
if errors:
|
||||
msg = "\n%s: encountered the following problems:\n" % (sys.argv[0],)
|
||||
for e in errors:
|
||||
lines = e.splitlines()
|
||||
msg += " " + lines[0] + '\n'.join(
|
||||
(" %s"%line for line in lines[1:]))
|
||||
raise SystemExit(msg)
|
||||
|
||||
|
||||
def action_print(m):
|
||||
output_content = m.output_content
|
||||
sys.stdout.write(output_content.joined)
|
||||
|
||||
|
||||
def action_ifchanged(m):
|
||||
if m.output_content.joined == m.input_content.joined:
|
||||
logger.debug("unmodified: %s", m.filename)
|
||||
raise AbortActions
|
||||
|
||||
|
||||
def action_replace(m):
|
||||
if m.filename == Filename.STDIN:
|
||||
raise Exception("Can't replace stdio in-place")
|
||||
logger.info("%s: *** modified ***", m.filename)
|
||||
atomic_write_file(m.filename, m.output_content)
|
||||
|
||||
|
||||
def action_external_command(command):
|
||||
import subprocess
|
||||
def action(m):
|
||||
bindir = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
env = os.environ
|
||||
env['PATH'] = env['PATH'] + ":" + bindir
|
||||
fullcmd = "%s %s %s" % (
|
||||
command, m.input_content_filename, m.output_content_filename)
|
||||
logger.debug("Executing external command: %s", fullcmd)
|
||||
ret = subprocess.call(fullcmd, shell=True, env=env)
|
||||
logger.debug("External command returned %d", ret)
|
||||
return action
|
||||
|
||||
|
||||
def action_query(prompt="Proceed?"):
|
||||
def action(m):
|
||||
p = prompt.format(filename=m.filename)
|
||||
print()
|
||||
print("%s [y/N] " % (p), end="")
|
||||
try:
|
||||
if input().strip().lower().startswith('y'):
|
||||
return True
|
||||
except KeyboardInterrupt:
|
||||
print("KeyboardInterrupt", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
print("Aborted")
|
||||
raise AbortActions
|
||||
return action
|
||||
|
||||
def symlink_callback(option, opt_str, value, parser):
|
||||
parser.values.actions = tuple(i for i in parser.values.actions if i not in
|
||||
symlink_callbacks.values())
|
||||
if value in symlink_callbacks:
|
||||
parser.values.actions = (symlink_callbacks[value],) + parser.values.actions
|
||||
else:
|
||||
raise optparse.OptionValueError("--symlinks must be one of 'error', 'follow', 'skip', or 'replace'. Got %r" % value)
|
||||
|
||||
symlinks_help = """\
|
||||
--symlinks=error (default; gives an error on symlinks),
|
||||
--symlinks=follow (follows symlinks),
|
||||
--symlinks=skip (skips symlinks),
|
||||
--symlinks=replace (replaces symlinks with the target file\
|
||||
"""
|
||||
|
||||
# Warning, the symlink actions will only work if they are run first.
|
||||
# Otherwise, output_content may already be cached
|
||||
def symlink_error(m):
|
||||
if m.filename == Filename.STDIN:
|
||||
return symlink_follow(m)
|
||||
if m.filename.islink:
|
||||
raise SystemExit("""\
|
||||
Error: %s appears to be a symlink. Use one of the following options to allow symlinks:
|
||||
%s
|
||||
""" % (m.filename, indent(symlinks_help, ' ')))
|
||||
|
||||
def symlink_follow(m):
|
||||
if m.filename == Filename.STDIN:
|
||||
return
|
||||
if m.filename.islink:
|
||||
logger.info("Following symlink %s" % m.filename)
|
||||
m.filename = m.filename.realpath
|
||||
|
||||
def symlink_skip(m):
|
||||
if m.filename == Filename.STDIN:
|
||||
return symlink_follow(m)
|
||||
if m.filename.islink:
|
||||
logger.info("Skipping symlink %s" % m.filename)
|
||||
raise AbortActions
|
||||
|
||||
def symlink_replace(m):
|
||||
if m.filename == Filename.STDIN:
|
||||
return symlink_follow(m)
|
||||
if m.filename.islink:
|
||||
logger.info("Replacing symlink %s" % m.filename)
|
||||
# The current behavior automatically replaces symlinks, so do nothing
|
||||
|
||||
symlink_callbacks = {
|
||||
'error': symlink_error,
|
||||
'follow': symlink_follow,
|
||||
'skip': symlink_skip,
|
||||
'replace': symlink_replace,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue