
#
# HISTORY
#
# 2020-06, Arjo Segers
#   Added 'urc' keyword to UtopyaRc initialization.
#


#-------------------------------------------------
# help
#-------------------------------------------------

"""
********************
``utopya_rc`` module
********************

The ``utopya_rc`` module holds the base class
for objects that have settings defined in a rcfile.

Class hierarchy
===============

The classes are defined according to following hierarchy:

* :py:class:`.UtopyaBase`

  * :py:class:`.UtopyaRc`

Classes
=======


"""

#-------------------------------------------------
# modules
#-------------------------------------------------

# modules:
import logging

# tools:
from utopya_base import UtopyaBase


#-------------------------------------------------
# rcfile class
#-------------------------------------------------

class UtopyaRc( UtopyaBase ) :

    """
    Base class for UTOPyA objects configured by a text file called the 'rcfile'.
    See the documentation of the :py:mod:`rc` module for details
    on the rcfile formating and preprocessing on reading.

    The settings are read on initialization if arguments 'rcfile' and 'rcbase' are provided,
    or later on by a call to :py:meth:`InitRc`.
    
    Use the :py:meth:`GetSetting` method to obtain the value of a setting.
    For example, if the rcfile 'test.rc' has content::
      
        ! sample setting:
        data.value    :  10
        
    this could be retrieved using::
      
        uto = UtopyaRc( 'test.rc' )
        n = uto.GetSetting( 'data.value', 'int' )
        
    The optional 'rcbase' defines the first part of the settings,
    and can be used to define groups of similar settings.
    For example, if the rcfile contains settings for 'Model-A' and 'Model-B'::
    
        ! sample setting:
        Model-A.data.value    :  10
        Model-B.data.value    :  20

    then the settings for 'Model-A' are selected using::
              
        uto = UtopyaRc( 'test.rc', rcbase='Model-A' )
        n = uto.GetSetting( 'data.value', 'int' )

    Settings that apply to all bases could be defined by a template::
      
        ! sample setting:
        *.data.value    :  10
    
    The following rcfile settings should be present
    to initialize the logging sytem::
        
       ! Enable debug messages ?
       ! If True, the logging level is 'DEBUG', otherwise 'INFO' :
       [<rcbase>.]logging.verbose       :  True
       
       ! logging format, empty for default:
       [<rcbase>.]logging.format        :  %(message)s

    In summary, the following methods are provided with this class:
        
    * :py:meth:`InitRc` : read rcfile if not done on initialization;
    * :py:meth:`GetSetting` : extract value of setting;
    * :py:meth:`ImportClass` : import class specified by an rcfile setting.
        
    """
    
    def __init__( self, rcfile=None, rcbase='', env={}, urc=None ) :
    
        """
        Initialize UTOPyA RC class.
        """
        
        # init base class:
        UtopyaBase.__init__( self )
        
        # initialize rcfile if provided
        if rcfile is not None :
            # check ...
            if urc is not None :
                logging.error( 'rcfile provided, but also urc ..' )
                raise Exception
            #endif
            # define rcfile object, read settings:
            self.InitRc( rcfile, rcbase, env=env )
        #
        elif urc is not None :
            # copy:
            self.rcf    = urc.rcf
            self.rcfile = urc.rcfile
            self.rcbase = urc.rcbase
        #
        else :
            # check ...
            if rcbase != '' :
                logging.error( 'rcbase provided without rcfile' )
                raise Exception
            #endif
            # not yet ..
            self.rcf    = None
            self.rcfile = None
            self.rcbase = None
        #endif

    #enddef __init__
    
    # *
    
    def InitRc( self, rcfile, rcbase='', env={} ) :
    
        """
        Initialize rcfile if not done on initialization.
        This also initializes the logging based on the rcfile settings.
        """
        
        # tools:
        import rc
        
        # define rcfile object, read settings:
        self.rcf = rc.RcFile( rcfile, env=env )
        # store arguments:
        self.rcfile = rcfile
        self.rcbase = rcbase

    #enddef InitRc
        
    
    # *
    
    def __del__( self ) :
    
        """
        Destructor for UTOPyA RC class.
        """
        
        # delete rcfile object:
        if hasattr(self,'rcf') : del self.rcf
        
    #enddef __del__
    
    # *
    
    def GetSetting( self, name, totype='', **kwargs ) :
    
        """
        Return value of the named entry in the rcfile::
            
            <name>       :  <value>
            
        If at initialization an optional 'rcbase' was provided,
        the entry that is searched for is prefixed by this base,
        or is a generic version::
            
            <rcbase>.<name>    :  <value>
            *.<name>           :  <value>
  
        Optional keyword arguments are pased to the :py:meth:`.RcFile.get` method.
        For example, a keyword argument 'default' could be
        used to set the return value if none of the keys is found.

        Note that double dots '..' in a name are replaced by a single '.',
        this is useful when the provided name is combined from some
        underlying parts and some of these start or end with a dot already.
        """
        
        # check ...
        if self.rcf is None :
            logging.error( 'no rcfile defined for object' )
            raise Exception
        #endif
        
        # form key:
        if self.rcbase is None :
            xkey = name
        else :
            xkey = self.rcbase+'.'+name
        #endif

        # remove double and leading seperations:
        xkey = xkey.replace('..','.').lstrip('.')
        
        # add all generic versions: 'a.b.c.c', '*.b.c.d', '*.c.d', ...
        keys = [xkey,'*.'+xkey]
        while '.' in xkey :
            bkey,xkey = xkey.split('.',1)
            keys.append( '*.'+xkey )
        #endwhile

        # flag:
        found = False
        # loop:
        for key in keys :
            # present ?
            if key in self.rcf.keys() :
                # read from rcfile:
                value = self.rcf.get( key, totype=totype, **kwargs )
                # set flag:
                found = True
            #endif
            # leave ?
            if found : break
        #endif
        # not found ?
        if not found :
            # default specified ?
            if 'default' in kwargs.keys() :
                value = kwargs['default']
            else :
                logging.error( 'none of the following key(s) found in: "%s"' % self.rcfile )
                for key in keys :
                    logging.error( '  %s' % key )
                #endfor
                raise Exception
            #endif
        #endif
        
        # ok:
        return value
        
    #enddef GetSetting
    
    # *
    
    def WriteSettings( self, filename ) :
    
        """
        Write evaluated settings to file.
        """
        
        # check ...
        if self.rcf is None :
            logging.error( 'no rcfile defined for object' )
            raise Exception
        #endif
        
        # write:
        import os
        self.rcf.WriteFile( filename )
        
    #enddef WriteSettings

    # *
    
    def ImportClass( self, name ) :

        """
        Import class from a module, 
        where the names are specified in the rcfile.
        
        For example, to import 'MyClass' from the module 'MyMod'
        defined in the file '/path/to/MyMod.py', use the following setting::
            
            [<rcbase>.]name      :  /path/to/MyMod.MyClass
        """
        
        # modules:
        import os
        import sys
        
        # tools:
        import utopya_tools
        
        # name of model module and class:
        moduleclass = self.GetSetting( name )
        
        # use tool function:
        cls = utopya_tools.ImportClass( moduleclass )
        
        # ok
        return cls
        
    #enddef ImportClass

#endclass UtopyaRc



#-------------------------------------------------
# end
#-------------------------------------------------


