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