Package svnmailer :: Module config
[hide private]

Source Code for Module svnmailer.config

  1  # -*- coding: utf-8 -*- 
  2  # pylint: disable-msg = W0201 
  3  # 
  4  # Copyright 2004-2006 André Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  Configfile parsing 
 19  """ 
 20  __author__    = "André Malo" 
 21  __docformat__ = "epytext en" 
 22  __all__       = [ 
 23      'ConfigFileSettings', 
 24      'Error', 
 25      'ConfigNotFoundError', 
 26      'ConfigMissingError', 
 27      'ConfigInvalidError', 
 28      'ConfigMappingSectionNotFoundError', 
 29      'ConfigMappingSpecInvalidError', 
 30      'ConfigSectionNotFoundError', 
 31      'ConfigOptionUnknownError', 
 32  ] 
 33   
 34  # global imports 
 35  import ConfigParser, sys, os 
 36  from svnmailer import settings, util 
 37   
 38   
 39  # Exceptions 
40 -class Error(Exception):
41 """ Base exception for this module """ 42 pass
43
44 -class ConfigNotFoundError(Error):
45 """ Config file not found """ 46 pass
47
48 -class ConfigMissingError(ConfigNotFoundError):
49 """ Config not specified and not found on default locations """ 50 pass
51
52 -class ConfigInvalidError(Error):
53 """ Config file has errors """ 54 pass
55
56 -class ConfigMappingSectionNotFoundError(ConfigInvalidError):
57 """ Config mapping section was not found """ 58 pass
59
60 -class ConfigMappingSpecInvalidError(ConfigInvalidError):
61 """ Config mapping spec was not recognized """ 62 pass
63
64 -class ConfigSectionNotFoundError(ConfigInvalidError):
65 """ Specified config section was not found """ 66 pass
67
68 -class ConfigOptionUnknownError(ConfigInvalidError):
69 """ An unknown option was parsed """ 70 pass
71 72
73 -class ConfigFileSettings(settings.Settings):
74 """ Provide settings from config 75 76 @cvar MAPSECTION: The mapping section name; if C{None}, 77 mapping is effectively disabled 78 @type MAPSECTION: C{str} 79 80 @ivar _config: The config object 81 @type _config: C{ConfigParser.ConfigParser} 82 """ 83 __implements__ = [settings.Settings] 84 85 MAPSECTION = "maps" 86
87 - def init(self, *args, **kwargs):
88 """ Implements the C{init} method of L{settings.Settings} 89 90 @exception ConfigInvalidError: invalid config options 91 @exception ConfigMissingError: see L{_loadConfig} 92 @exception ConfigNotFoundError: see L{_loadConfig} 93 @exception ConfigSectionNotFoundError: see L{_passConfig} 94 @exception ConfigOptionUnkownError: see L{_passConfig} 95 @exception ConfigMappingSpecInvalidError: see L{_applyMaps} 96 @exception ConfigMappingSectionNotFoundError: see L{_getPlainMap} 97 """ 98 try: 99 self._init(*args, **kwargs) 100 except (ValueError, TypeError, UnicodeError, ConfigParser.Error), exc: 101 raise ConfigInvalidError, str(exc), sys.exc_info()[2]
102 103
104 - def _init(self, options):
105 """ Actual implementation of C{self.init()} 106 107 @param options: runtime options 108 @type options: C{optparse.OptionParser} 109 110 @exception ConfigMissingError: see L{_loadConfig} 111 @exception ConfigNotFoundError: see L{_loadConfig} 112 @exception ConfigSectionNotFoundError: see L{_passConfig} 113 @exception ConfigOptionUnkownError: see L{_passConfig} 114 @exception ConfigMappingSpecInvalidError: see L{_applyMaps} 115 @exception ConfigMappingSectionNotFoundError: see L{_getPlainMap} 116 """ 117 self._initRuntime(options) 118 self._loadConfig() # needs runtime 119 self._initGeneral() # needs _config 120 self._initGroups() # needs _config and general
121 122
123 - def _initGroups(self):
124 """ Initializes the Group config """ 125 defaults = self._getGroupDefaults() 126 ddict = self._getDefaultGroupDict(defaults) 127 128 for group in self._config.sections(): 129 ddict["_name"] = group 130 container = self.getGroupContainer(**ddict) 131 self._passConfig(container, group) 132 self.groups.append(container) 133 134 if not self.groups: 135 self.groups.append(self.getGroupContainer(**defaults._dict_))
136 137
138 - def _getDefaultGroupDict(self, container):
139 """ Returns the default group dict 140 141 @param container: The default container 142 @type container: C{svnmailer.settings.GroupSettingsContainer} 143 144 @return: The default dict 145 @rtype: C{dict} 146 """ 147 ddict = dict(container._dict_) 148 ddict.update({ 149 "_def_for_repos": container.for_repos, 150 "_def_for_paths": container.for_paths, 151 }) 152 153 return ddict
154 155
156 - def _getGroupDefaults(self):
157 """ Returns the default groups container 158 159 @return: The defaults (groupcontainer without maps) 160 @rtype: C{svnmailer.settings.GroupSettingsContainer} 161 """ 162 defaults = self.getDefaultGroupContainer( 163 _name = "defaults", 164 diff_command = self.general.diff_command, 165 cia_rpc_server = self.general.cia_rpc_server, 166 ) 167 try: 168 self._passConfig(defaults, "defaults") 169 except ConfigSectionNotFoundError: 170 # [defaults] is optional 171 pass 172 else: 173 self._config.remove_section('defaults') 174 175 return defaults
176 177
178 - def _initGeneral(self):
179 """ Initializes the general config 180 181 @exception ConfigSectionNotFoundError: [general] not found 182 """ 183 self.general = self.getGeneralContainer() 184 self._passConfig(self.general, 'general') 185 self._config.remove_section('general')
186 187
188 - def _initRuntime(self, options):
189 """ Initializes the runtime from options 190 191 @param options: runtime options 192 @type options: C{optparse.OptionParser} 193 """ 194 # This is needed for every container 195 self._fcharset_ = options.path_encoding 196 197 self.runtime = self.getRuntimeContainer( 198 revision = options.revision, 199 repository = options.repository, 200 path_encoding = options.path_encoding, 201 debug = options.debug, 202 config = options.config, 203 mode = options.mode, 204 author = options.author, 205 propname = options.propname, 206 action = options.action, 207 )
208 209
210 - def _passConfig(self, container, section):
211 """ Passes the options to the specified container 212 213 @param container: The container object 214 @type container: C{svnmailer.util.Struct} 215 216 @param section: The config section name 217 @type section: C{str} 218 219 @exception ConfigSectionNotFoundError: The specified section was 220 not found in the config file 221 @exception ConfigOptionUnkownError: There was an unknown 222 config option in the config file. 223 """ 224 try: 225 for option in self._config.options(section): 226 # options starting with _ are for internal usage 227 if option[:1] in ('_', '-'): 228 raise ConfigOptionUnknownError( 229 "Unknown option '%s' in section [%s]" % 230 (option, section) 231 ) 232 233 try: 234 container._set_( 235 option.replace('-', '_'), 236 self._config.get(section, option, raw = True) 237 ) 238 except AttributeError: 239 raise ConfigOptionUnknownError( 240 "Unknown option '%s' in section [%s]" % 241 (option, section) 242 ) 243 except ConfigParser.NoSectionError, exc: 244 raise ConfigSectionNotFoundError(str(exc))
245 246
247 - def _loadConfig(self):
248 """ Parse config file 249 250 @return: parsed config 251 @rtype: C{ConfigParser.ConfigParser} 252 253 @exception ConfigNotFoundError: some configfile could not 254 be opened 255 @exception ConfigMissingError: see L{_findConfig} 256 @exception ConfigMappingSpecInvalidError: see L{_applyMaps} 257 @exception ConfigMappingSectionNotFoundError: see L{_getPlainMap} 258 """ 259 config_fp = self._findConfig() 260 self._config = self._createConfigParser() 261 try: 262 self._config.readfp(config_fp, config_fp.name) 263 config_fp.close() 264 except IOError, exc: 265 raise ConfigNotFoundError("%s: %s" % (config_fp.name, str(exc))) 266 267 if self._config.has_section("general"): 268 self._applyCharset() 269 self._applyIncludes(config_fp.name) 270 271 self._applyMaps()
272 273
274 - def _createConfigParser(self):
275 """ Returns a ConfigParser instance 276 277 @return: The ConfigParser instance 278 @rtype: C{ConfigParser.ConfigParser} 279 """ 280 return ConfigParser.ConfigParser()
281 282
283 - def _findConfig(self, _file = file):
284 """ Finds and opens the main config file 285 286 @param _file: The function to open the file 287 @type _file: C{callable} 288 289 @return: The open descriptor 290 @rtype: file like object 291 292 @exception ConfigMissingError: config neither specified nor 293 on default locations found. Default locations are (tried 294 in that order): 295 - <repos>/conf/mailer.conf 296 - <scriptdir>/mailer.conf 297 - /etc/svn-mailer.conf 298 @exception ConfigNotFoundError: specified configfile could not 299 be opened 300 """ 301 import errno 302 303 config_file = self.runtime.config 304 if config_file: 305 try: 306 return config_file == '-' and sys.stdin or _file(config_file) 307 except IOError, exc: 308 raise ConfigNotFoundError("%s: %s" % (config_file, str(exc))) 309 310 for config_file in self._getDefaultConfigFiles(): 311 try: 312 return _file(config_file) 313 except IOError, exc: 314 # try next one only if not found 315 if exc[0] != errno.ENOENT: 316 raise ConfigNotFoundError("%s: %s" % ( 317 config_file, str(exc) 318 )) 319 320 raise ConfigMissingError("No config file found")
321 322
323 - def _applyMaps(self):
324 """ Resolves all map definitions 325 326 @TODO: raise an error on unknown options 327 328 @exception ConfigMappingSpecInvalidError: The mapping spec was 329 invalid 330 @exception ConfigMappingSectionNotFoundError: see L{_getPlainMap} 331 """ 332 section = self.MAPSECTION 333 if section is None or not self._config.has_section(section): 334 return 335 336 self._maps_ = {} 337 remove_sections = [section] 338 for option in self._config.options(section): 339 if option[:1] in ('_', '-'): 340 raise ConfigOptionUnknownError( 341 "Unknown option '%s' in section [%s]" % 342 (option, section) 343 ) 344 345 value = self._config.get(section, option, raw = True) 346 if value[:1] == '[' and value[-1:] == ']': 347 this_section = value[1:-1] 348 self._maps_[option.replace('-', '_')] = \ 349 self._getPlainMap(this_section) 350 remove_sections.append(this_section) 351 else: 352 raise ConfigMappingSpecInvalidError( 353 "Invalid mapping specification %r = %r" % (option, value) 354 ) 355 356 for name in dict.fromkeys(remove_sections).keys(): 357 self._config.remove_section(name)
358 359
360 - def _getPlainMap(self, section):
361 """ Returns a plain map for a particular section 362 363 @param section: The mapping section 364 @type section: C{str} 365 366 @return: The mapping function 367 @rtype: C{callable} 368 369 @exception ConfigMappingSectionNotFoundError: The specified 370 section was not found 371 """ 372 try: 373 mdict = dict([ 374 (option, self._config.get(section, option, raw = True)) 375 for option in self._config.options(section) 376 ]) 377 except ConfigParser.NoSectionError, exc: 378 raise ConfigMappingSectionNotFoundError(str(exc)) 379 380 def mapfunc(value): 381 """ Mapping function """ 382 return mdict.get(value, value)
383 384 return mapfunc
385 386
387 - def _applyIncludes(self, origfile, _file = file):
388 """ Applies the includes found in [general] 389 390 @param origfile: original filename 391 @type origfile: C{str} 392 393 @param _file: The function to open the file 394 @type _file: C{callable} 395 396 @exception ConfigNotFoundError: Error reading an included file 397 """ 398 opt = "include_config" 399 try: 400 try: 401 includes = self._config.get("general", opt, raw = True).strip() 402 except ConfigParser.NoOptionError: 403 opt = "include-config" 404 includes = self._config.get("general", opt, raw = True).strip() 405 except ConfigParser.NoOptionError: 406 # don't even ignore 407 pass 408 else: 409 self._config.remove_option("general", opt) 410 if not len(includes): 411 return 412 413 origpath = os.path.dirname(os.path.abspath(origfile)) 414 includes = [ 415 util.filename.toLocale( 416 config_file, self._charset_, self.runtime.path_encoding 417 ) 418 for config_file in util.splitCommand(includes) if config_file 419 ] 420 421 for config_file in includes: 422 try: 423 config_fp = _file(os.path.join(origpath, config_file)) 424 self._config.readfp(config_fp, config_fp.name) 425 config_fp.close() 426 except IOError, exc: 427 raise ConfigNotFoundError("%s: %s" % ( 428 config_file, str(exc) 429 ))
430 431
432 - def _applyCharset(self):
433 """ Applies the charset found in [general] """ 434 opt = "config_charset" 435 try: 436 try: 437 charset = self._config.get("general", opt, raw = True).strip() 438 except ConfigParser.NoOptionError: 439 opt = "config-charset" 440 charset = self._config.get("general", opt, raw = True).strip() 441 except ConfigParser.NoOptionError: 442 # don't even ignore 443 pass 444 else: 445 self._config.remove_option("general", opt) 446 if charset: 447 self._charset_ = charset
448 449
450 - def _getDefaultConfigFiles(self, _os = os, _sys = sys):
451 """ Returns the default config files 452 453 @return: The list 454 @rtype: C{list} 455 """ 456 argv0 = util.filename.fromLocale( 457 _sys.argv[0], self.runtime.path_encoding 458 ) 459 if isinstance(argv0, unicode): 460 candidates = [util.filename.toLocale( 461 name, locale_enc = self.runtime.path_encoding 462 ) for name in [ 463 _os.path.join( 464 self.runtime.repository, u'conf', u'mailer.conf' 465 ), 466 _os.path.join(_os.path.dirname(argv0), u'mailer.conf'), 467 u'/etc/svn-mailer.conf', 468 ] 469 ] 470 else: 471 # --path-encoding=none 472 candidates = [ 473 _os.path.join(self.runtime.repository, 'conf', 'mailer.conf'), 474 _os.path.join(_os.path.dirname(argv0), 'mailer.conf'), 475 _os.path.join(_os.path.sep, "etc", "svn-mailer.conf"), 476 ] 477 478 return candidates
479