baseparser.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. """Base option parser setup"""
  2. from __future__ import absolute_import
  3. import sys
  4. import optparse
  5. import os
  6. import re
  7. import textwrap
  8. from distutils.util import strtobool
  9. from pip._vendor.six import string_types
  10. from pip._vendor.six.moves import configparser
  11. from pip.locations import (
  12. legacy_config_file, config_basename, running_under_virtualenv,
  13. site_config_files
  14. )
  15. from pip.utils import appdirs, get_terminal_size
  16. _environ_prefix_re = re.compile(r"^PIP_", re.I)
  17. class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
  18. """A prettier/less verbose help formatter for optparse."""
  19. def __init__(self, *args, **kwargs):
  20. # help position must be aligned with __init__.parseopts.description
  21. kwargs['max_help_position'] = 30
  22. kwargs['indent_increment'] = 1
  23. kwargs['width'] = get_terminal_size()[0] - 2
  24. optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
  25. def format_option_strings(self, option):
  26. return self._format_option_strings(option, ' <%s>', ', ')
  27. def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '):
  28. """
  29. Return a comma-separated list of option strings and metavars.
  30. :param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
  31. :param mvarfmt: metavar format string - evaluated as mvarfmt % metavar
  32. :param optsep: separator
  33. """
  34. opts = []
  35. if option._short_opts:
  36. opts.append(option._short_opts[0])
  37. if option._long_opts:
  38. opts.append(option._long_opts[0])
  39. if len(opts) > 1:
  40. opts.insert(1, optsep)
  41. if option.takes_value():
  42. metavar = option.metavar or option.dest.lower()
  43. opts.append(mvarfmt % metavar.lower())
  44. return ''.join(opts)
  45. def format_heading(self, heading):
  46. if heading == 'Options':
  47. return ''
  48. return heading + ':\n'
  49. def format_usage(self, usage):
  50. """
  51. Ensure there is only one newline between usage and the first heading
  52. if there is no description.
  53. """
  54. msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ")
  55. return msg
  56. def format_description(self, description):
  57. # leave full control over description to us
  58. if description:
  59. if hasattr(self.parser, 'main'):
  60. label = 'Commands'
  61. else:
  62. label = 'Description'
  63. # some doc strings have initial newlines, some don't
  64. description = description.lstrip('\n')
  65. # some doc strings have final newlines and spaces, some don't
  66. description = description.rstrip()
  67. # dedent, then reindent
  68. description = self.indent_lines(textwrap.dedent(description), " ")
  69. description = '%s:\n%s\n' % (label, description)
  70. return description
  71. else:
  72. return ''
  73. def format_epilog(self, epilog):
  74. # leave full control over epilog to us
  75. if epilog:
  76. return epilog
  77. else:
  78. return ''
  79. def indent_lines(self, text, indent):
  80. new_lines = [indent + line for line in text.split('\n')]
  81. return "\n".join(new_lines)
  82. class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
  83. """Custom help formatter for use in ConfigOptionParser that updates
  84. the defaults before expanding them, allowing them to show up correctly
  85. in the help listing"""
  86. def expand_default(self, option):
  87. if self.parser is not None:
  88. self.parser.update_defaults(self.parser.defaults)
  89. return optparse.IndentedHelpFormatter.expand_default(self, option)
  90. class CustomOptionParser(optparse.OptionParser):
  91. def insert_option_group(self, idx, *args, **kwargs):
  92. """Insert an OptionGroup at a given position."""
  93. group = self.add_option_group(*args, **kwargs)
  94. self.option_groups.pop()
  95. self.option_groups.insert(idx, group)
  96. return group
  97. @property
  98. def option_list_all(self):
  99. """Get a list of all options, including those in option groups."""
  100. res = self.option_list[:]
  101. for i in self.option_groups:
  102. res.extend(i.option_list)
  103. return res
  104. class ConfigOptionParser(CustomOptionParser):
  105. """Custom option parser which updates its defaults by checking the
  106. configuration files and environmental variables"""
  107. isolated = False
  108. def __init__(self, *args, **kwargs):
  109. self.config = configparser.RawConfigParser()
  110. self.name = kwargs.pop('name')
  111. self.isolated = kwargs.pop("isolated", False)
  112. self.files = self.get_config_files()
  113. if self.files:
  114. self.config.read(self.files)
  115. assert self.name
  116. optparse.OptionParser.__init__(self, *args, **kwargs)
  117. def get_config_files(self):
  118. # the files returned by this method will be parsed in order with the
  119. # first files listed being overridden by later files in standard
  120. # ConfigParser fashion
  121. config_file = os.environ.get('PIP_CONFIG_FILE', False)
  122. if config_file == os.devnull:
  123. return []
  124. # at the base we have any site-wide configuration
  125. files = list(site_config_files)
  126. # per-user configuration next
  127. if not self.isolated:
  128. if config_file and os.path.exists(config_file):
  129. files.append(config_file)
  130. else:
  131. # This is the legacy config file, we consider it to be a lower
  132. # priority than the new file location.
  133. files.append(legacy_config_file)
  134. # This is the new config file, we consider it to be a higher
  135. # priority than the legacy file.
  136. files.append(
  137. os.path.join(
  138. appdirs.user_config_dir("pip"),
  139. config_basename,
  140. )
  141. )
  142. # finally virtualenv configuration first trumping others
  143. if running_under_virtualenv():
  144. venv_config_file = os.path.join(
  145. sys.prefix,
  146. config_basename,
  147. )
  148. if os.path.exists(venv_config_file):
  149. files.append(venv_config_file)
  150. return files
  151. def check_default(self, option, key, val):
  152. try:
  153. return option.check_value(key, val)
  154. except optparse.OptionValueError as exc:
  155. print("An error occurred during configuration: %s" % exc)
  156. sys.exit(3)
  157. def update_defaults(self, defaults):
  158. """Updates the given defaults with values from the config files and
  159. the environ. Does a little special handling for certain types of
  160. options (lists)."""
  161. # Then go and look for the other sources of configuration:
  162. config = {}
  163. # 1. config files
  164. for section in ('global', self.name):
  165. config.update(
  166. self.normalize_keys(self.get_config_section(section))
  167. )
  168. # 2. environmental variables
  169. if not self.isolated:
  170. config.update(self.normalize_keys(self.get_environ_vars()))
  171. # Then set the options with those values
  172. for key, val in config.items():
  173. option = self.get_option(key)
  174. if option is not None:
  175. # ignore empty values
  176. if not val:
  177. continue
  178. if option.action in ('store_true', 'store_false', 'count'):
  179. val = strtobool(val)
  180. if option.action == 'append':
  181. val = val.split()
  182. val = [self.check_default(option, key, v) for v in val]
  183. else:
  184. val = self.check_default(option, key, val)
  185. defaults[option.dest] = val
  186. return defaults
  187. def normalize_keys(self, items):
  188. """Return a config dictionary with normalized keys regardless of
  189. whether the keys were specified in environment variables or in config
  190. files"""
  191. normalized = {}
  192. for key, val in items:
  193. key = key.replace('_', '-')
  194. if not key.startswith('--'):
  195. key = '--%s' % key # only prefer long opts
  196. normalized[key] = val
  197. return normalized
  198. def get_config_section(self, name):
  199. """Get a section of a configuration"""
  200. if self.config.has_section(name):
  201. return self.config.items(name)
  202. return []
  203. def get_environ_vars(self):
  204. """Returns a generator with all environmental vars with prefix PIP_"""
  205. for key, val in os.environ.items():
  206. if _environ_prefix_re.search(key):
  207. yield (_environ_prefix_re.sub("", key).lower(), val)
  208. def get_default_values(self):
  209. """Overridding to make updating the defaults after instantiation of
  210. the option parser possible, update_defaults() does the dirty work."""
  211. if not self.process_default_values:
  212. # Old, pre-Optik 1.5 behaviour.
  213. return optparse.Values(self.defaults)
  214. defaults = self.update_defaults(self.defaults.copy()) # ours
  215. for option in self._get_all_options():
  216. default = defaults.get(option.dest)
  217. if isinstance(default, string_types):
  218. opt_str = option.get_opt_string()
  219. defaults[option.dest] = option.check_value(opt_str, default)
  220. return optparse.Values(defaults)
  221. def error(self, msg):
  222. self.print_usage(sys.stderr)
  223. self.exit(2, "%s\n" % msg)