text: # vi: set ft=python sts=4 ts=4 sw=4 et:
  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
  #
  #   See COPYING file distributed along with the reproman package for the
  #   copyright and license terms.
  #
  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
  """Registry-like monster for now simply borrowed from bigmess/pymvpa
  
  TODO: integration with cmdline etc
  """
  
  __docformat__ = 'restructuredtext'
  
  import os.path
  from appdirs import AppDirs
  
  from os.path import join as opj
  
  from .support.configparserinc import SafeConfigParserWithIncludes
  
  LOCATIONS_DOC = """
      1. '/etc/reproman/reproman.cfg'
      2. 'reproman/reproman.cfg' in all directories defined by $XDG_CONFIG_DIRS
         (by default: /etc/xdg/)
      3. 'reproman/reproman.cfg' in $XDG_CONFIG_HOME (by default: ~/.config/)
      4. '.reproman/reproman.cfg', relative to the current directory""".lstrip()
  
  
  class ConfigManager(SafeConfigParserWithIncludes, object):
      """Central configuration registry for reproman.
  
      The purpose of this class is to collect all configurable settings
      used by various parts of reproman. It is fairly simple and does
      only little more than the standard Python ConfigParser. Like
      ConfigParser it is blind to the data that it stores, i.e. no type
      checking is performed.
  
      Configuration files (INI syntax) in multiple location are parsed
      when a class instance is created or whenever `Config.reload()` is
      called later on.  Files are read and parsed in the order described by
      `LOCATIONS_DOC`.
  
      Moreover, the constructor takes an optional argument with a list
      of additional file names to parse afterwards.
  
      In addition to configuration files, this class also looks for
      special environment variables to read settings from. Names of such
      variables have to start with `REPROMAN_` following by the an
      optional section name and the variable name itself ('_' as
      delimiter). If no section name is provided, the variables will be
      associated with section `general`. Some examples::
  
          REPROMAN_VERBOSE=1
  
      will become::
  
          [general]
          verbose = 1
  
      However, `REPROMAN_VERBOSE_OUTPUT=stdout` becomes::
  
          [verbose]
          output = stdout
  
      Any length of variable name as allowed, e.g. REPROMAN_SEC1_LONG_NAME=1
      becomes::
  
          [sec1]
          long name = 1
  
      Settings from custom configuration files (specified by the constructor
      argument) have the highest priority and override settings found in any of
      the config files read from default locations (which are themselves read in
      the order stated above -- overwriting earlier configuration settings
      accordingly). Finally, the content of any `REPROMAN_*` environment variables
      overrides any settings read from any file.
      """
  
      dirs = AppDirs("reproman", "reproman.org")
  
      # things we want to count on to be available
      _DEFAULTS = {'general': {'verbose': '1'}}
  
      def __init__(self, filenames=None, load_default=True):
          """Initialization reads settings from config files and env. variables.
  
          Parameters
          ----------
          filenames : list of filenames
          load_default: bool, optional
            Either to load default files (e.g. system/user-wide etc)
          """
          self._super.__init__()
  
          # set critical defaults
          for sec, params in ConfigManager._DEFAULTS.items():
              self.add_section(sec)
              for key, value in params.items():
                  self.set(sec, key, value)
  
          self.__cfg_filenames = []
          self._load_default = load_default
  
          # now get the setting
          # TODO: look into loading/interaction with config being lazy or better more efficient...
          # could cut on startup time a bit.
          self.reload(filenames)
  
      @property
      def _super(self):
          return super(ConfigManager, self)
  
      def _get_default_file_candidates(self):
          # list of filenames to parse (custom plus some standard ones),
          # they are listed in the order they need to be parsed to
          # guarantee a sane configuration file cascade
          homedir = os.path.expanduser('~')  # seems to be useless ???
          cfg_file_candidates = [
              # shipped-with config
              # opj(os.path.dirname(__file__), 'reproman.cfg'),
              # system config
              '/etc/reproman/reproman.cfg']
          # XDG system config
          cfg_file_candidates.append(opj(self.dirs.site_config_dir,
                                         'reproman.cfg'))
  
          # XDG user config
          home_cfg_base_path = self.dirs.user_config_dir
          if os.path.isabs(home_cfg_base_path):
              cfg_file_candidates.append(opj(home_cfg_base_path, 'reproman.cfg'))
  
          # current dir config
          cfg_file_candidates.append(opj('.reproman', 'reproman.cfg'))
          return cfg_file_candidates
  
      def _get_file_candidates(self):
          cfg_file_candidates = self._get_default_file_candidates() if self._load_default else []
          # runtime config
          cfg_file_candidates += self.__cfg_filenames
          return cfg_file_candidates
  
  
      def reload(self, filenames=None):
          """Re-read settings from all configured locations.
          """
          # store additional config file names
          if not filenames is None:
              self.__cfg_filenames = filenames
  
          # read local and user-specific config
          self.read(self._get_file_candidates())
  
          # now look for variables in the environment
          pref = 'REPROMAN_'
          for var in [v for v in os.environ.keys() if v.startswith(pref)]:
              # strip leading 'REPROMAN_' and lower case entries
              svar = var[len(pref):].lower()
  
              # section is next element in name (or 'general' if simple name)
              if not svar.count('_'):
                  sec = 'general'
              else:
                  cut = svar.find('_')
                  sec = svar[:cut]
                  svar = svar[cut + 1:].replace('_', ' ')
  
              # check if section is already known and add it if not
              if not self.has_section(sec):
                  self.add_section(sec)
  
              # set value
              self.set(sec, svar, os.environ[var])
  
      def get(self, section, option, default=None, **kwargs):
          """Wrapper around SafeConfigParser.get() with a custom default value.
  
          This method simply wraps the base class method, but adds a `default`
          keyword argument. The value of `default` is returned whenever the
          config parser does not have the requested option and/or section.
          """
          if not self.has_option(section, option):
              return default
  
          try:
              return self._super.get(section, option, **kwargs)
          except ValueError as e:
              # provide somewhat descriptive error
              raise ValueError(
                  "Failed to obtain value from configuration for %s.%s. "
                  "Original exception was: %s" % (section, option, e))
  
      def getpath(self, *args, **kwargs):
          """Wrapper around get to do additional path treatments such as expanduser
  
          See documentation for `get`
          """
          val = self.get(*args, **kwargs)
          return os.path.expanduser(val)
  
      def getboolean(self, section, option, default=None):
          """Wrapper around SafeConfigParser.getboolean() with a custom default.
  
          This method simply wraps the base class method, but adds a `default`
          keyword argument. The value of `default` is returned whenever the
          config parser does not have the requested option and/or section.
          """
          if not self.has_option(section, option):
              if isinstance(default, bool):
                  return default
              else:
                  # compatibility layer for py3 version of ConfigParser
                  if hasattr(self, '_boolean_states'):
                      boolean_states = self._boolean_states
                  else:
                      boolean_states = self.BOOLEAN_STATES  # there is no BOOLEAN_STATES ???
                  if default is None or (default.lower() not in boolean_states):
                      raise ValueError('Not a boolean: %s' % default)
                  return boolean_states[default.lower()]
  
          return self._super.getboolean(section, option)
  
      ##REF: Name was automagically refactored
      def get_as_dtype(self, section, option, dtype, default=None):
          """Convenience method to query options with a custom default and type
  
          This method simply wraps the base class method, but adds a
          `default` keyword argument. The value of `default` is returned
          whenever the config parser does not have the requested option
          and/or section.
  
          In addition, the returned value is converted into the
          specified `dtype`.
          """
          if not self.has_option(section, option):
              return default
          try:
              return self._super._get(section, dtype, option)
          except ValueError as e:
              # provide somewhat descriptive error
              raise ValueError(
                  "Failed to obtain value from configuration for %s.%s. "
                  "Original exception was: %s" % (section, option, e))
  