python (3.11.7)
       1  from distutils.util import convert_path
       2  from distutils import log
       3  from distutils.errors import DistutilsOptionError
       4  import distutils
       5  import os
       6  import configparser
       7  
       8  from setuptools import Command
       9  
      10  __all__ = ['config_file', 'edit_config', 'option_base', 'setopt']
      11  
      12  
      13  def config_file(kind="local"):
      14      """Get the filename of the distutils, local, global, or per-user config
      15  
      16      `kind` must be one of "local", "global", or "user"
      17      """
      18      if kind == 'local':
      19          return 'setup.cfg'
      20      if kind == 'global':
      21          return os.path.join(
      22              os.path.dirname(distutils.__file__), 'distutils.cfg'
      23          )
      24      if kind == 'user':
      25          dot = os.name == 'posix' and '.' or ''
      26          return os.path.expanduser(convert_path("~/%spydistutils.cfg" % dot))
      27      raise ValueError(
      28          "config_file() type must be 'local', 'global', or 'user'", kind
      29      )
      30  
      31  
      32  def edit_config(filename, settings, dry_run=False):
      33      """Edit a configuration file to include `settings`
      34  
      35      `settings` is a dictionary of dictionaries or ``None`` values, keyed by
      36      command/section name.  A ``None`` value means to delete the entire section,
      37      while a dictionary lists settings to be changed or deleted in that section.
      38      A setting of ``None`` means to delete that setting.
      39      """
      40      log.debug("Reading configuration from %s", filename)
      41      opts = configparser.RawConfigParser()
      42      opts.optionxform = lambda x: x
      43      opts.read([filename])
      44      for section, options in settings.items():
      45          if options is None:
      46              log.info("Deleting section [%s] from %s", section, filename)
      47              opts.remove_section(section)
      48          else:
      49              if not opts.has_section(section):
      50                  log.debug("Adding new section [%s] to %s", section, filename)
      51                  opts.add_section(section)
      52              for option, value in options.items():
      53                  if value is None:
      54                      log.debug(
      55                          "Deleting %s.%s from %s",
      56                          section, option, filename
      57                      )
      58                      opts.remove_option(section, option)
      59                      if not opts.options(section):
      60                          log.info("Deleting empty [%s] section from %s",
      61                                   section, filename)
      62                          opts.remove_section(section)
      63                  else:
      64                      log.debug(
      65                          "Setting %s.%s to %r in %s",
      66                          section, option, value, filename
      67                      )
      68                      opts.set(section, option, value)
      69  
      70      log.info("Writing %s", filename)
      71      if not dry_run:
      72          with open(filename, 'w') as f:
      73              opts.write(f)
      74  
      75  
      76  class ESC[4;38;5;81moption_base(ESC[4;38;5;149mCommand):
      77      """Abstract base class for commands that mess with config files"""
      78  
      79      user_options = [
      80          ('global-config', 'g',
      81           "save options to the site-wide distutils.cfg file"),
      82          ('user-config', 'u',
      83           "save options to the current user's pydistutils.cfg file"),
      84          ('filename=', 'f',
      85           "configuration file to use (default=setup.cfg)"),
      86      ]
      87  
      88      boolean_options = [
      89          'global-config', 'user-config',
      90      ]
      91  
      92      def initialize_options(self):
      93          self.global_config = None
      94          self.user_config = None
      95          self.filename = None
      96  
      97      def finalize_options(self):
      98          filenames = []
      99          if self.global_config:
     100              filenames.append(config_file('global'))
     101          if self.user_config:
     102              filenames.append(config_file('user'))
     103          if self.filename is not None:
     104              filenames.append(self.filename)
     105          if not filenames:
     106              filenames.append(config_file('local'))
     107          if len(filenames) > 1:
     108              raise DistutilsOptionError(
     109                  "Must specify only one configuration file option",
     110                  filenames
     111              )
     112          self.filename, = filenames
     113  
     114  
     115  class ESC[4;38;5;81msetopt(ESC[4;38;5;149moption_base):
     116      """Save command-line options to a file"""
     117  
     118      description = "set an option in setup.cfg or another config file"
     119  
     120      user_options = [
     121          ('command=', 'c', 'command to set an option for'),
     122          ('option=', 'o', 'option to set'),
     123          ('set-value=', 's', 'value of the option'),
     124          ('remove', 'r', 'remove (unset) the value'),
     125      ] + option_base.user_options
     126  
     127      boolean_options = option_base.boolean_options + ['remove']
     128  
     129      def initialize_options(self):
     130          option_base.initialize_options(self)
     131          self.command = None
     132          self.option = None
     133          self.set_value = None
     134          self.remove = None
     135  
     136      def finalize_options(self):
     137          option_base.finalize_options(self)
     138          if self.command is None or self.option is None:
     139              raise DistutilsOptionError("Must specify --command *and* --option")
     140          if self.set_value is None and not self.remove:
     141              raise DistutilsOptionError("Must specify --set-value or --remove")
     142  
     143      def run(self):
     144          edit_config(
     145              self.filename, {
     146                  self.command: {self.option.replace('-', '_'): self.set_value}
     147              },
     148              self.dry_run
     149          )