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