1from distutils.util import convert_path 2from distutils import log 3from distutils.errors import DistutilsOptionError 4import distutils 5import os 6import configparser 7 8from setuptools import Command 9 10__all__ = ['config_file', 'edit_config', 'option_base', 'setopt'] 11 12 13def 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 32def 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 76class option_base(Command): 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 115class setopt(option_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 ) 150