python (3.11.7)
       1  """Base option parser setup"""
       2  
       3  import logging
       4  import optparse
       5  import shutil
       6  import sys
       7  import textwrap
       8  from contextlib import suppress
       9  from typing import Any, Dict, Generator, List, Tuple
      10  
      11  from pip._internal.cli.status_codes import UNKNOWN_ERROR
      12  from pip._internal.configuration import Configuration, ConfigurationError
      13  from pip._internal.utils.misc import redact_auth_from_url, strtobool
      14  
      15  logger = logging.getLogger(__name__)
      16  
      17  
      18  class ESC[4;38;5;81mPrettyHelpFormatter(ESC[4;38;5;149moptparseESC[4;38;5;149m.ESC[4;38;5;149mIndentedHelpFormatter):
      19      """A prettier/less verbose help formatter for optparse."""
      20  
      21      def __init__(self, *args: Any, **kwargs: Any) -> None:
      22          # help position must be aligned with __init__.parseopts.description
      23          kwargs["max_help_position"] = 30
      24          kwargs["indent_increment"] = 1
      25          kwargs["width"] = shutil.get_terminal_size()[0] - 2
      26          super().__init__(*args, **kwargs)
      27  
      28      def format_option_strings(self, option: optparse.Option) -> str:
      29          return self._format_option_strings(option)
      30  
      31      def _format_option_strings(
      32          self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "
      33      ) -> str:
      34          """
      35          Return a comma-separated list of option strings and metavars.
      36  
      37          :param option:  tuple of (short opt, long opt), e.g: ('-f', '--format')
      38          :param mvarfmt: metavar format string
      39          :param optsep:  separator
      40          """
      41          opts = []
      42  
      43          if option._short_opts:
      44              opts.append(option._short_opts[0])
      45          if option._long_opts:
      46              opts.append(option._long_opts[0])
      47          if len(opts) > 1:
      48              opts.insert(1, optsep)
      49  
      50          if option.takes_value():
      51              assert option.dest is not None
      52              metavar = option.metavar or option.dest.lower()
      53              opts.append(mvarfmt.format(metavar.lower()))
      54  
      55          return "".join(opts)
      56  
      57      def format_heading(self, heading: str) -> str:
      58          if heading == "Options":
      59              return ""
      60          return heading + ":\n"
      61  
      62      def format_usage(self, usage: str) -> str:
      63          """
      64          Ensure there is only one newline between usage and the first heading
      65          if there is no description.
      66          """
      67          msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), "  "))
      68          return msg
      69  
      70      def format_description(self, description: str) -> str:
      71          # leave full control over description to us
      72          if description:
      73              if hasattr(self.parser, "main"):
      74                  label = "Commands"
      75              else:
      76                  label = "Description"
      77              # some doc strings have initial newlines, some don't
      78              description = description.lstrip("\n")
      79              # some doc strings have final newlines and spaces, some don't
      80              description = description.rstrip()
      81              # dedent, then reindent
      82              description = self.indent_lines(textwrap.dedent(description), "  ")
      83              description = f"{label}:\n{description}\n"
      84              return description
      85          else:
      86              return ""
      87  
      88      def format_epilog(self, epilog: str) -> str:
      89          # leave full control over epilog to us
      90          if epilog:
      91              return epilog
      92          else:
      93              return ""
      94  
      95      def indent_lines(self, text: str, indent: str) -> str:
      96          new_lines = [indent + line for line in text.split("\n")]
      97          return "\n".join(new_lines)
      98  
      99  
     100  class ESC[4;38;5;81mUpdatingDefaultsHelpFormatter(ESC[4;38;5;149mPrettyHelpFormatter):
     101      """Custom help formatter for use in ConfigOptionParser.
     102  
     103      This is updates the defaults before expanding them, allowing
     104      them to show up correctly in the help listing.
     105  
     106      Also redact auth from url type options
     107      """
     108  
     109      def expand_default(self, option: optparse.Option) -> str:
     110          default_values = None
     111          if self.parser is not None:
     112              assert isinstance(self.parser, ConfigOptionParser)
     113              self.parser._update_defaults(self.parser.defaults)
     114              assert option.dest is not None
     115              default_values = self.parser.defaults.get(option.dest)
     116          help_text = super().expand_default(option)
     117  
     118          if default_values and option.metavar == "URL":
     119              if isinstance(default_values, str):
     120                  default_values = [default_values]
     121  
     122              # If its not a list, we should abort and just return the help text
     123              if not isinstance(default_values, list):
     124                  default_values = []
     125  
     126              for val in default_values:
     127                  help_text = help_text.replace(val, redact_auth_from_url(val))
     128  
     129          return help_text
     130  
     131  
     132  class ESC[4;38;5;81mCustomOptionParser(ESC[4;38;5;149moptparseESC[4;38;5;149m.ESC[4;38;5;149mOptionParser):
     133      def insert_option_group(
     134          self, idx: int, *args: Any, **kwargs: Any
     135      ) -> optparse.OptionGroup:
     136          """Insert an OptionGroup at a given position."""
     137          group = self.add_option_group(*args, **kwargs)
     138  
     139          self.option_groups.pop()
     140          self.option_groups.insert(idx, group)
     141  
     142          return group
     143  
     144      @property
     145      def option_list_all(self) -> List[optparse.Option]:
     146          """Get a list of all options, including those in option groups."""
     147          res = self.option_list[:]
     148          for i in self.option_groups:
     149              res.extend(i.option_list)
     150  
     151          return res
     152  
     153  
     154  class ESC[4;38;5;81mConfigOptionParser(ESC[4;38;5;149mCustomOptionParser):
     155      """Custom option parser which updates its defaults by checking the
     156      configuration files and environmental variables"""
     157  
     158      def __init__(
     159          self,
     160          *args: Any,
     161          name: str,
     162          isolated: bool = False,
     163          **kwargs: Any,
     164      ) -> None:
     165          self.name = name
     166          self.config = Configuration(isolated)
     167  
     168          assert self.name
     169          super().__init__(*args, **kwargs)
     170  
     171      def check_default(self, option: optparse.Option, key: str, val: Any) -> Any:
     172          try:
     173              return option.check_value(key, val)
     174          except optparse.OptionValueError as exc:
     175              print(f"An error occurred during configuration: {exc}")
     176              sys.exit(3)
     177  
     178      def _get_ordered_configuration_items(
     179          self,
     180      ) -> Generator[Tuple[str, Any], None, None]:
     181          # Configuration gives keys in an unordered manner. Order them.
     182          override_order = ["global", self.name, ":env:"]
     183  
     184          # Pool the options into different groups
     185          section_items: Dict[str, List[Tuple[str, Any]]] = {
     186              name: [] for name in override_order
     187          }
     188          for section_key, val in self.config.items():
     189              # ignore empty values
     190              if not val:
     191                  logger.debug(
     192                      "Ignoring configuration key '%s' as it's value is empty.",
     193                      section_key,
     194                  )
     195                  continue
     196  
     197              section, key = section_key.split(".", 1)
     198              if section in override_order:
     199                  section_items[section].append((key, val))
     200  
     201          # Yield each group in their override order
     202          for section in override_order:
     203              for key, val in section_items[section]:
     204                  yield key, val
     205  
     206      def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
     207          """Updates the given defaults with values from the config files and
     208          the environ. Does a little special handling for certain types of
     209          options (lists)."""
     210  
     211          # Accumulate complex default state.
     212          self.values = optparse.Values(self.defaults)
     213          late_eval = set()
     214          # Then set the options with those values
     215          for key, val in self._get_ordered_configuration_items():
     216              # '--' because configuration supports only long names
     217              option = self.get_option("--" + key)
     218  
     219              # Ignore options not present in this parser. E.g. non-globals put
     220              # in [global] by users that want them to apply to all applicable
     221              # commands.
     222              if option is None:
     223                  continue
     224  
     225              assert option.dest is not None
     226  
     227              if option.action in ("store_true", "store_false"):
     228                  try:
     229                      val = strtobool(val)
     230                  except ValueError:
     231                      self.error(
     232                          "{} is not a valid value for {} option, "  # noqa
     233                          "please specify a boolean value like yes/no, "
     234                          "true/false or 1/0 instead.".format(val, key)
     235                      )
     236              elif option.action == "count":
     237                  with suppress(ValueError):
     238                      val = strtobool(val)
     239                  with suppress(ValueError):
     240                      val = int(val)
     241                  if not isinstance(val, int) or val < 0:
     242                      self.error(
     243                          "{} is not a valid value for {} option, "  # noqa
     244                          "please instead specify either a non-negative integer "
     245                          "or a boolean value like yes/no or false/true "
     246                          "which is equivalent to 1/0.".format(val, key)
     247                      )
     248              elif option.action == "append":
     249                  val = val.split()
     250                  val = [self.check_default(option, key, v) for v in val]
     251              elif option.action == "callback":
     252                  assert option.callback is not None
     253                  late_eval.add(option.dest)
     254                  opt_str = option.get_opt_string()
     255                  val = option.convert_value(opt_str, val)
     256                  # From take_action
     257                  args = option.callback_args or ()
     258                  kwargs = option.callback_kwargs or {}
     259                  option.callback(option, opt_str, val, self, *args, **kwargs)
     260              else:
     261                  val = self.check_default(option, key, val)
     262  
     263              defaults[option.dest] = val
     264  
     265          for key in late_eval:
     266              defaults[key] = getattr(self.values, key)
     267          self.values = None
     268          return defaults
     269  
     270      def get_default_values(self) -> optparse.Values:
     271          """Overriding to make updating the defaults after instantiation of
     272          the option parser possible, _update_defaults() does the dirty work."""
     273          if not self.process_default_values:
     274              # Old, pre-Optik 1.5 behaviour.
     275              return optparse.Values(self.defaults)
     276  
     277          # Load the configuration, or error out in case of an error
     278          try:
     279              self.config.load()
     280          except ConfigurationError as err:
     281              self.exit(UNKNOWN_ERROR, str(err))
     282  
     283          defaults = self._update_defaults(self.defaults.copy())  # ours
     284          for option in self._get_all_options():
     285              assert option.dest is not None
     286              default = defaults.get(option.dest)
     287              if isinstance(default, str):
     288                  opt_str = option.get_opt_string()
     289                  defaults[option.dest] = option.check_value(opt_str, default)
     290          return optparse.Values(defaults)
     291  
     292      def error(self, msg: str) -> None:
     293          self.print_usage(sys.stderr)
     294          self.exit(UNKNOWN_ERROR, f"{msg}\n")