#
##
##  SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.4  (Thu Nov 16 18:07:39 CET 2023)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
#
"""Numerical data reader
"""
__all__ = ['splitFloat', 'readData']
import re
_re_float = re.compile(r'[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?')
_re_float_string = re.compile(r'(?P<float>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)(?P<string>.*)')
[docs]def splitFloat(s):
    """Match a floating point number at the beginning of a string.
    Parameters
    ----------
    s: str
        A string that starts with a numerical part (float).
    Returns
    -------
    tuple (float, str) | None:
        If the beginning of the string matches a floating point number,
        returns a tuple with the float and the remainder of the string.
        Else, return None
    Examples
    --------
    >>> splitFloat('123e4rt345e6')
    [1230000.0, 'rt345e6']
    """
    m = _re_float_string.match(s)
    if m:
        return [float(m.group('float')), m.group('string')]
    return None 
[docs]def convertData(s, to):
    """Convert numerical data to a requested type or units.
    Converts a string including a numerical value and optional units
    to the requested type or units.
    Parameters
    ----------
    s: str
        A string containing a numerical value and an optional units string,
        optionally separated by whitespace. E.g. '10 kg'.
    to: str
        The units or type to which the value from the string ``s`` is to be
        converted. If ``s`` contains units, the specified ``to`` units should
        be compatible (i.e. for the same physical quantity).
        If a field has no units, the target ``to`` value can also be
        'int' or 'float'.
    Returns
    -------
    int | float
        The numerical value read from the string ``s``, converted to the
        specified units or type ``to``.
    Examples
    --------
    >>> convertData('1 inch', 'mm')
    25.4
    >>> convertData('1', 'mm')
    1.0
    >>> convertData('14.5e3', 'kg')
    14500.0
    >>> convertData('12 inch', 'float')
    12.0
    >>> convertData('17.5', 'int')
    17
    Warning
    -------
    You need to have the GNU ``units`` command installed for the unit
    conversion to work.
    """
    # IMPORT HERE: LATE IMPORT
    from pyformex.plugins import units
    s = s.strip()
    m = _re_float.match(s)
    if not m:
        raise ValueError(f"No value found in '{s}'")
    if m.end() == len(s) or to in ['int', 'float']:
        # no units or no units conversion: handle ourself
        val = float(s[:m.end()])
        if to == 'int':
            val = int(val)
    else:
        # let 'units' do the conversion
        val = float(units.convertUnits(s, to))
    return val 
[docs]def readData(s, to, strict=False):
    """Read values from a string matching the units/type specifications.
    This is a powerful function for reading, interpreting and converting
    numerical data from a string.
    Parameters
    ----------
    s: str
        A string containing comma-separated fields. Each field is either
        a numerical value, or a numerical value followed by a units string.
        The value and the units can optionally be separated by whitespace.
    to: list of str
        A list of units to which the fields in the string ``s`` should be
        converted. The length of the list is usually equal to the number
        of fields in the string ``s``.
        If a field has units, the corresponding ``to`` should be a compatible
        unit (i.e. for the same physical quantity). If a field has no units,
        the target ``to`` value can also be 'int' or 'float'.
    strict: bool
        If True, the length of the ``to`` list should exactly match the
        number of fields in ``s``. The default (False) allows unequal lengths
        and will only proceed until the shortest is exhausted.
    Returns
    -------
    list
        A list of int/float values read from the string s.
        With ``strict=True`` the list will have the same length as the
        number of fields in ``s`` and the number of items in ``to``.
        Else, it will have the shortest length of the two.
        Where the field contains a units designator, the value is converted
        to the requested ``to`` units.
        designator.Fields in the string s are separated by
    Examples
    --------
    >>> readData('1 inch', ['mm'])
    [25.4]
    >>> readData('12, 13s, 14.5e3, 12 inch, 1hr, 31kg, 5MPa',
    ...     ['int','float','kg','cm','s','g','kN/m**2'])
    [12, 13.0, 14500.0, 30.48, 3600.0, 31000.0, 5000.0]
    Warning
    -------
    You need to have the GNU ``units`` command installed for the unit
    conversion to work.
    """
    data = s.split(',')
    if strict and len(data) != len(to):
        raise ValueError(f"'{s} does not match {to}")
    return [convertData(si, ti) for ti, si in zip(to, data)] 
# End