pip/src/pip/_internal/cli/parser.py

266 lines
9.3 KiB
Python
Raw Normal View History

"""Base option parser setup"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
2017-06-01 07:55:42 +02:00
import logging
import optparse
2017-05-16 12:16:30 +02:00
import sys
2013-01-18 22:25:15 +01:00
import textwrap
2009-11-20 09:46:21 +01:00
from distutils.util import strtobool
from pip._vendor.six import string_types
2017-05-16 12:16:30 +02:00
from pip._internal.cli.status_codes import UNKNOWN_ERROR
2018-01-24 19:35:15 +01:00
from pip._internal.configuration import Configuration, ConfigurationError
from pip._internal.utils.compat import get_terminal_size
2017-06-01 07:55:42 +02:00
logger = logging.getLogger(__name__)
class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
"""A prettier/less verbose help formatter for optparse."""
2012-09-01 20:35:19 +02:00
def __init__(self, *args, **kwargs):
2012-12-07 11:25:03 +01:00
# help position must be aligned with __init__.parseopts.description
2013-01-18 22:25:15 +01:00
kwargs['max_help_position'] = 30
2012-12-10 02:03:02 +01:00
kwargs['indent_increment'] = 1
2012-09-01 20:35:19 +02:00
kwargs['width'] = get_terminal_size()[0] - 2
optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
def format_option_strings(self, option):
return self._format_option_strings(option, ' <%s>', ', ')
def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '):
"""
Return a comma-separated list of option strings and metavars.
:param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
:param mvarfmt: metavar format string - evaluated as mvarfmt % metavar
:param optsep: separator
"""
opts = []
2012-09-01 20:35:19 +02:00
if option._short_opts:
opts.append(option._short_opts[0])
if option._long_opts:
opts.append(option._long_opts[0])
if len(opts) > 1:
opts.insert(1, optsep)
if option.takes_value():
metavar = option.metavar or option.dest.lower()
2013-01-18 22:25:15 +01:00
opts.append(mvarfmt % metavar.lower())
return ''.join(opts)
def format_heading(self, heading):
2012-09-01 20:35:19 +02:00
if heading == 'Options':
return ''
return heading + ':\n'
def format_usage(self, usage):
2012-09-01 20:35:19 +02:00
"""
Ensure there is only one newline between usage and the first heading
if there is no description.
2012-09-01 20:35:19 +02:00
"""
2013-01-18 22:25:15 +01:00
msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ")
return msg
def format_description(self, description):
# leave full control over description to us
if description:
2013-01-18 22:25:15 +01:00
if hasattr(self.parser, 'main'):
2013-02-16 21:51:57 +01:00
label = 'Commands'
2013-01-18 22:25:15 +01:00
else:
2013-02-16 21:51:57 +01:00
label = 'Description'
2014-04-30 07:33:04 +02:00
# some doc strings have initial newlines, some don't
2013-02-16 21:51:57 +01:00
description = description.lstrip('\n')
2014-03-26 23:24:19 +01:00
# some doc strings have final newlines and spaces, some don't
2013-02-16 21:51:57 +01:00
description = description.rstrip()
2014-03-26 23:24:19 +01:00
# dedent, then reindent
2013-02-16 21:51:57 +01:00
description = self.indent_lines(textwrap.dedent(description), " ")
description = '%s:\n%s\n' % (label, description)
return description
else:
return ''
def format_epilog(self, epilog):
# leave full control over epilog to us
if epilog:
return epilog
else:
return ''
2013-01-18 22:25:15 +01:00
def indent_lines(self, text, indent):
new_lines = [indent + line for line in text.split('\n')]
return "\n".join(new_lines)
2012-09-01 20:35:19 +02:00
class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
"""Custom help formatter for use in ConfigOptionParser.
This is updates the defaults before expanding them, allowing
them to show up correctly in the help listing.
"""
def expand_default(self, option):
if self.parser is not None:
self.parser._update_defaults(self.parser.defaults)
return optparse.IndentedHelpFormatter.expand_default(self, option)
class CustomOptionParser(optparse.OptionParser):
2016-07-28 21:11:53 +02:00
def insert_option_group(self, idx, *args, **kwargs):
"""Insert an OptionGroup at a given position."""
group = self.add_option_group(*args, **kwargs)
self.option_groups.pop()
self.option_groups.insert(idx, group)
return group
2012-12-14 14:35:00 +01:00
@property
def option_list_all(self):
"""Get a list of all options, including those in option groups."""
res = self.option_list[:]
for i in self.option_groups:
res.extend(i.option_list)
return res
class ConfigOptionParser(CustomOptionParser):
2013-09-05 11:54:15 +02:00
"""Custom option parser which updates its defaults by checking the
configuration files and environmental variables"""
def __init__(self, *args, **kwargs):
self.name = kwargs.pop('name')
isolated = kwargs.pop("isolated", False)
2017-01-18 16:09:52 +01:00
self.config = Configuration(isolated)
assert self.name
optparse.OptionParser.__init__(self, *args, **kwargs)
def check_default(self, option, key, val):
try:
return option.check_value(key, val)
except optparse.OptionValueError as exc:
print("An error occurred during configuration: %s" % exc)
sys.exit(3)
def _get_ordered_configuration_items(self):
# Configuration gives keys in an unordered manner. Order them.
override_order = ["global", self.name, ":env:"]
# Pool the options into different groups
section_items = {name: [] for name in override_order}
2017-06-01 07:55:42 +02:00
for section_key, val in self.config.items():
# ignore empty values
if not val:
2017-06-01 07:55:42 +02:00
logger.debug(
"Ignoring configuration key '%s' as it's value is empty.",
section_key
)
continue
2017-06-01 07:55:42 +02:00
section, key = section_key.split(".", 1)
if section in override_order:
section_items[section].append((key, val))
# Yield each group in their override order
for section in override_order:
for key, val in section_items[section]:
yield key, val
def _update_defaults(self, defaults):
"""Updates the given defaults with values from the config files and
the environ. Does a little special handling for certain types of
options (lists)."""
2016-12-23 10:09:15 +01:00
# Accumulate complex default state.
self.values = optparse.Values(self.defaults)
late_eval = set()
# Then set the options with those values
for key, val in self._get_ordered_configuration_items():
# '--' because configuration supports only long names
option = self.get_option('--' + key)
# Ignore options not present in this parser. E.g. non-globals put
# in [global] by users that want them to apply to all applicable
# commands.
if option is None:
continue
if option.action in ('store_true', 'store_false', 'count'):
try:
val = strtobool(val)
except ValueError:
2018-07-25 19:27:55 +02:00
error_msg = invalid_config_error_message(
option.action, key, val
)
2018-07-25 19:27:55 +02:00
self.error(error_msg)
elif option.action == 'append':
val = val.split()
val = [self.check_default(option, key, v) for v in val]
elif option.action == 'callback':
late_eval.add(option.dest)
opt_str = option.get_opt_string()
val = option.convert_value(opt_str, val)
# From take_action
args = option.callback_args or ()
kwargs = option.callback_kwargs or {}
option.callback(option, opt_str, val, self, *args, **kwargs)
else:
val = self.check_default(option, key, val)
defaults[option.dest] = val
for key in late_eval:
defaults[key] = getattr(self.values, key)
self.values = None
return defaults
def get_default_values(self):
2016-06-10 21:27:07 +02:00
"""Overriding to make updating the defaults after instantiation of
the option parser possible, _update_defaults() does the dirty work."""
if not self.process_default_values:
# Old, pre-Optik 1.5 behaviour.
return optparse.Values(self.defaults)
# Load the configuration, or error out in case of an error
try:
self.config.load()
except ConfigurationError as err:
self.exit(UNKNOWN_ERROR, str(err))
defaults = self._update_defaults(self.defaults.copy()) # ours
for option in self._get_all_options():
default = defaults.get(option.dest)
2011-03-15 20:49:48 +01:00
if isinstance(default, string_types):
opt_str = option.get_opt_string()
defaults[option.dest] = option.check_value(opt_str, default)
return optparse.Values(defaults)
def error(self, msg):
self.print_usage(sys.stderr)
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
2018-07-25 19:27:55 +02:00
2018-07-25 19:31:17 +02:00
2018-07-25 19:27:55 +02:00
def invalid_config_error_message(action, key, val):
"""Returns a better error message when invalid configuration option
is provided."""
if action in ('store_true', 'store_false'):
return ("{0} is not a valid value for {1} option, "
"please specify a boolean value like yes/no, "
"true/false or 1/0 instead.").format(val, key)
return ("{0} is not a valid value for {1} option, "
"please specify a numerical value like 1/0 "
"instead.").format(val, key)