311 lines
11 KiB
Python
311 lines
11 KiB
Python
|
#!/usr/bin/env python
|
||
|
from __future__ import absolute_import
|
||
|
|
||
|
import locale
|
||
|
import logging
|
||
|
import os
|
||
|
import optparse
|
||
|
import warnings
|
||
|
|
||
|
import sys
|
||
|
|
||
|
# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks,
|
||
|
# but if invoked (i.e. imported), it will issue a warning to stderr if socks
|
||
|
# isn't available. requests unconditionally imports urllib3's socks contrib
|
||
|
# module, triggering this warning. The warning breaks DEP-8 tests (because of
|
||
|
# the stderr output) and is just plain annoying in normal usage. I don't want
|
||
|
# to add socks as yet another dependency for pip, nor do I want to allow-stder
|
||
|
# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to
|
||
|
# be done before the import of pip.vcs.
|
||
|
from pip._vendor.urllib3.exceptions import DependencyWarning
|
||
|
warnings.filterwarnings("ignore", category=DependencyWarning) # noqa
|
||
|
|
||
|
# We want to inject the use of SecureTransport as early as possible so that any
|
||
|
# references or sessions or what have you are ensured to have it, however we
|
||
|
# only want to do this in the case that we're running on macOS and the linked
|
||
|
# OpenSSL is too old to handle TLSv1.2
|
||
|
try:
|
||
|
import ssl
|
||
|
except ImportError:
|
||
|
pass
|
||
|
else:
|
||
|
# Checks for OpenSSL 1.0.1 on MacOS
|
||
|
if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f:
|
||
|
try:
|
||
|
from pip._vendor.urllib3.contrib import securetransport
|
||
|
except (ImportError, OSError):
|
||
|
pass
|
||
|
else:
|
||
|
securetransport.inject_into_urllib3()
|
||
|
|
||
|
from pip import __version__
|
||
|
from pip._internal import cmdoptions
|
||
|
from pip._internal.exceptions import CommandError, PipError
|
||
|
from pip._internal.utils.misc import get_installed_distributions, get_prog
|
||
|
from pip._internal.utils import deprecation
|
||
|
from pip._internal.vcs import git, mercurial, subversion, bazaar # noqa
|
||
|
from pip._internal.baseparser import (
|
||
|
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||
|
)
|
||
|
from pip._internal.commands import get_summaries, get_similar_commands
|
||
|
from pip._internal.commands import commands_dict
|
||
|
from pip._vendor.urllib3.exceptions import InsecureRequestWarning
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
# Hide the InsecureRequestWarning from urllib3
|
||
|
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||
|
|
||
|
|
||
|
def autocomplete():
|
||
|
"""Command and option completion for the main option parser (and options)
|
||
|
and its subcommands (and options).
|
||
|
|
||
|
Enable by sourcing one of the completion shell scripts (bash, zsh or fish).
|
||
|
"""
|
||
|
# Don't complete if user hasn't sourced bash_completion file.
|
||
|
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||
|
return
|
||
|
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||
|
cword = int(os.environ['COMP_CWORD'])
|
||
|
try:
|
||
|
current = cwords[cword - 1]
|
||
|
except IndexError:
|
||
|
current = ''
|
||
|
|
||
|
subcommands = [cmd for cmd, summary in get_summaries()]
|
||
|
options = []
|
||
|
# subcommand
|
||
|
try:
|
||
|
subcommand_name = [w for w in cwords if w in subcommands][0]
|
||
|
except IndexError:
|
||
|
subcommand_name = None
|
||
|
|
||
|
parser = create_main_parser()
|
||
|
# subcommand options
|
||
|
if subcommand_name:
|
||
|
# special case: 'help' subcommand has no options
|
||
|
if subcommand_name == 'help':
|
||
|
sys.exit(1)
|
||
|
# special case: list locally installed dists for show and uninstall
|
||
|
should_list_installed = (
|
||
|
subcommand_name in ['show', 'uninstall'] and
|
||
|
not current.startswith('-')
|
||
|
)
|
||
|
if should_list_installed:
|
||
|
installed = []
|
||
|
lc = current.lower()
|
||
|
for dist in get_installed_distributions(local_only=True):
|
||
|
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
|
||
|
installed.append(dist.key)
|
||
|
# if there are no dists installed, fall back to option completion
|
||
|
if installed:
|
||
|
for dist in installed:
|
||
|
print(dist)
|
||
|
sys.exit(1)
|
||
|
|
||
|
subcommand = commands_dict[subcommand_name]()
|
||
|
|
||
|
for opt in subcommand.parser.option_list_all:
|
||
|
if opt.help != optparse.SUPPRESS_HELP:
|
||
|
for opt_str in opt._long_opts + opt._short_opts:
|
||
|
options.append((opt_str, opt.nargs))
|
||
|
|
||
|
# filter out previously specified options from available options
|
||
|
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||
|
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||
|
# filter options by current input
|
||
|
options = [(k, v) for k, v in options if k.startswith(current)]
|
||
|
# get completion type given cwords and available subcommand options
|
||
|
completion_type = get_path_completion_type(
|
||
|
cwords, cword, subcommand.parser.option_list_all,
|
||
|
)
|
||
|
# get completion files and directories if ``completion_type`` is
|
||
|
# ``<file>``, ``<dir>`` or ``<path>``
|
||
|
if completion_type:
|
||
|
options = auto_complete_paths(current, completion_type)
|
||
|
options = ((opt, 0) for opt in options)
|
||
|
for option in options:
|
||
|
opt_label = option[0]
|
||
|
# append '=' to options which require args
|
||
|
if option[1] and option[0][:2] == "--":
|
||
|
opt_label += '='
|
||
|
print(opt_label)
|
||
|
else:
|
||
|
# show main parser options only when necessary
|
||
|
|
||
|
opts = [i.option_list for i in parser.option_groups]
|
||
|
opts.append(parser.option_list)
|
||
|
opts = (o for it in opts for o in it)
|
||
|
if current.startswith('-'):
|
||
|
for opt in opts:
|
||
|
if opt.help != optparse.SUPPRESS_HELP:
|
||
|
subcommands += opt._long_opts + opt._short_opts
|
||
|
else:
|
||
|
# get completion type given cwords and all available options
|
||
|
completion_type = get_path_completion_type(cwords, cword, opts)
|
||
|
if completion_type:
|
||
|
subcommands = auto_complete_paths(current, completion_type)
|
||
|
|
||
|
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||
|
sys.exit(1)
|
||
|
|
||
|
|
||
|
def get_path_completion_type(cwords, cword, opts):
|
||
|
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
|
||
|
|
||
|
:param cwords: same as the environmental variable ``COMP_WORDS``
|
||
|
:param cword: same as the environmental variable ``COMP_CWORD``
|
||
|
:param opts: The available options to check
|
||
|
:return: path completion type (``file``, ``dir``, ``path`` or None)
|
||
|
"""
|
||
|
if cword < 2 or not cwords[cword - 2].startswith('-'):
|
||
|
return
|
||
|
for opt in opts:
|
||
|
if opt.help == optparse.SUPPRESS_HELP:
|
||
|
continue
|
||
|
for o in str(opt).split('/'):
|
||
|
if cwords[cword - 2].split('=')[0] == o:
|
||
|
if any(x in ('path', 'file', 'dir')
|
||
|
for x in opt.metavar.split('/')):
|
||
|
return opt.metavar
|
||
|
|
||
|
|
||
|
def auto_complete_paths(current, completion_type):
|
||
|
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
|
||
|
and directories starting with ``current``; otherwise only list directories
|
||
|
starting with ``current``.
|
||
|
|
||
|
:param current: The word to be completed
|
||
|
:param completion_type: path completion type(`file`, `path` or `dir`)i
|
||
|
:return: A generator of regular files and/or directories
|
||
|
"""
|
||
|
directory, filename = os.path.split(current)
|
||
|
current_path = os.path.abspath(directory)
|
||
|
# Don't complete paths if they can't be accessed
|
||
|
if not os.access(current_path, os.R_OK):
|
||
|
return
|
||
|
filename = os.path.normcase(filename)
|
||
|
# list all files that start with ``filename``
|
||
|
file_list = (x for x in os.listdir(current_path)
|
||
|
if os.path.normcase(x).startswith(filename))
|
||
|
for f in file_list:
|
||
|
opt = os.path.join(current_path, f)
|
||
|
comp_file = os.path.normcase(os.path.join(directory, f))
|
||
|
# complete regular files when there is not ``<dir>`` after option
|
||
|
# complete directories when there is ``<file>``, ``<path>`` or
|
||
|
# ``<dir>``after option
|
||
|
if completion_type != 'dir' and os.path.isfile(opt):
|
||
|
yield comp_file
|
||
|
elif os.path.isdir(opt):
|
||
|
yield os.path.join(comp_file, '')
|
||
|
|
||
|
|
||
|
def create_main_parser():
|
||
|
parser_kw = {
|
||
|
'usage': '\n%prog <command> [options]',
|
||
|
'add_help_option': False,
|
||
|
'formatter': UpdatingDefaultsHelpFormatter(),
|
||
|
'name': 'global',
|
||
|
'prog': get_prog(),
|
||
|
}
|
||
|
|
||
|
parser = ConfigOptionParser(**parser_kw)
|
||
|
parser.disable_interspersed_args()
|
||
|
|
||
|
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
|
parser.version = 'pip %s from %s (python %s)' % (
|
||
|
__version__, pip_pkg_dir, sys.version[:3],
|
||
|
)
|
||
|
|
||
|
# add the general options
|
||
|
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||
|
parser.add_option_group(gen_opts)
|
||
|
|
||
|
parser.main = True # so the help formatter knows
|
||
|
|
||
|
# create command listing for description
|
||
|
command_summaries = get_summaries()
|
||
|
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
|
||
|
parser.description = '\n'.join(description)
|
||
|
|
||
|
return parser
|
||
|
|
||
|
|
||
|
def parseopts(args):
|
||
|
parser = create_main_parser()
|
||
|
|
||
|
# Note: parser calls disable_interspersed_args(), so the result of this
|
||
|
# call is to split the initial args into the general options before the
|
||
|
# subcommand and everything else.
|
||
|
# For example:
|
||
|
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
||
|
# general_options: ['--timeout==5']
|
||
|
# args_else: ['install', '--user', 'INITools']
|
||
|
general_options, args_else = parser.parse_args(args)
|
||
|
|
||
|
# --version
|
||
|
if general_options.version:
|
||
|
sys.stdout.write(parser.version)
|
||
|
sys.stdout.write(os.linesep)
|
||
|
sys.exit()
|
||
|
|
||
|
# pip || pip help -> print_help()
|
||
|
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||
|
parser.print_help()
|
||
|
sys.exit()
|
||
|
|
||
|
# the subcommand name
|
||
|
cmd_name = args_else[0]
|
||
|
|
||
|
if cmd_name not in commands_dict:
|
||
|
guess = get_similar_commands(cmd_name)
|
||
|
|
||
|
msg = ['unknown command "%s"' % cmd_name]
|
||
|
if guess:
|
||
|
msg.append('maybe you meant "%s"' % guess)
|
||
|
|
||
|
raise CommandError(' - '.join(msg))
|
||
|
|
||
|
# all the args without the subcommand
|
||
|
cmd_args = args[:]
|
||
|
cmd_args.remove(cmd_name)
|
||
|
|
||
|
return cmd_name, cmd_args
|
||
|
|
||
|
|
||
|
def check_isolated(args):
|
||
|
isolated = False
|
||
|
|
||
|
if "--isolated" in args:
|
||
|
isolated = True
|
||
|
|
||
|
return isolated
|
||
|
|
||
|
|
||
|
def main(args=None):
|
||
|
if args is None:
|
||
|
args = sys.argv[1:]
|
||
|
|
||
|
# Configure our deprecation warnings to be sent through loggers
|
||
|
deprecation.install_warning_logger()
|
||
|
|
||
|
autocomplete()
|
||
|
|
||
|
try:
|
||
|
cmd_name, cmd_args = parseopts(args)
|
||
|
except PipError as exc:
|
||
|
sys.stderr.write("ERROR: %s" % exc)
|
||
|
sys.stderr.write(os.linesep)
|
||
|
sys.exit(1)
|
||
|
|
||
|
# Needed for locale.getpreferredencoding(False) to work
|
||
|
# in pip._internal.utils.encoding.auto_decode
|
||
|
try:
|
||
|
locale.setlocale(locale.LC_ALL, '')
|
||
|
except locale.Error as e:
|
||
|
# setlocale can apparently crash if locale are uninitialized
|
||
|
logger.debug("Ignoring error %s when setting locale", e)
|
||
|
command = commands_dict[cmd_name](isolated=check_isolated(cmd_args))
|
||
|
return command.main(cmd_args)
|