# This software is open source software available under the BSD-3 license.
#
# Copyright (c) 2020 Triad National Security, LLC. All rights reserved.
# Copyright (c) 2020 Lawrence Livermore National Security, LLC. All rights
# reserved.
# Copyright (c) 2020 UT-Battelle, LLC. All rights reserved.
#
# Additional copyright and license information can be found in the LICENSE file
# distributed with this code, or at
# https://raw.githubusercontent.com/MPAS-Dev/MPAS-Analysis/master/LICENSE
"""
A configuratin parser class for MPAS analysis.  MpasAnalysisConfigParser adds
the capabilities to get an option including a default value
(``getWithDefault(section, option, default, ...)``) and to get options
that are lists, tuples, dicts, etc (``getExpression(section, option)``).
"""
# Authors
# -------
# Xylar Asay-Davis, Phillip J. Wolfram
from __future__ import absolute_import, division, print_function, \
    unicode_literals
import numbers
import ast
import six
import numpy as np
from six.moves.configparser import ConfigParser
if six.PY3:
    xrange = range
npallow = dict(linspace=np.linspace, xrange=xrange, range=range, array=np.array,
               arange=np.arange, pi=np.pi, Pi=np.pi, int=int, __builtins__=None)
class MpasAnalysisConfigParser(ConfigParser):
[docs]    def getWithDefault(self, section, option, default):
        """
        Get an option, using the supplied default value if the option is not
        present.
        Parameters
        ----------
        section : str
            The section in the config file
        option : str
            The option in the config file
        default : {bool, int, float, list, tuple, dict, str}
            The default value if the option and/or section is not found, used
            to determine the type of the option if it *is* found. If
            ``default`` is a list, tuple, or dict, ``getExpression(...)`` is
            used if the option is present in the config file.
        """
        # Authors
        # -------
        # Xylar Asay-Davis
        if self.has_section(section):
            if self.has_option(section, option):
                if isinstance(default, bool):
                    return self.getboolean(section, option)
                elif isinstance(default, numbers.Integral):
                    return self.getint(section, option)
                elif isinstance(default, numbers.Real):
                    return self.getfloat(section, option)
                elif isinstance(default, (list, tuple, dict)):
                    return self.getExpression(section, option)
                else:
                    return self.get(section, option)
        # we didn't find the entry so set it to the default
        if not self.has_section(section):
            self.add_section(section)
        self.set(section, option, str(default))
        return default 
[docs]    def getExpression(self, section, option, elementType=None,
                      usenumpyfunc=False):
        """
        Get an option as an expression (typically a list, though tuples and
        dicts are also available).  The expression is required to have valid
        python syntax, so that string entries are required to be in single or
        double quotes.
        Parameters
        ----------
        section : str
            The section in the config file
        option : str
            The option in the config file
        elementType : {bool, int, float, list, tuple, str}, optional
            If supplied, each element in a list or tuple, or
            each value in a dictionary are cast to this type.  This is likely
            most useful for ensuring that all elements in a list of numbers are
            of type float, rather than int, when the distinction is important.
        usenumpyfunc : bool, optional
            If ``True``, the expression is evaluated including functionality
            from the numpy package (which can be referenced either as ``numpy``
            or ``np``).
        """
        # Authors
        # -------
        # Xylar Asay-Davis, Phillip J. Wolfram
        expressionString = self.get(section, option)
        if usenumpyfunc:
            assert '__' not in expressionString, \
                
"'__' is not allowed in {} "\
                
"for `usenumpyfunc=True`".format(expressionString)
            sanitizedstr = expressionString.replace('np.', '')\
                                           
.replace('numpy.', '')\
                                           
.replace('__', '')
            result = eval(sanitizedstr, npallow)
        else:
            result = ast.literal_eval(expressionString)
        if elementType is not None:
            if isinstance(result, (list, tuple)):
                result = [elementType(element) for element in result]
            elif isinstance(result, dict):
                for key in result:
                    result[key] = elementType(result[key])
        return result