# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-
"""
Provides a set of parsing utility functions.
.. todo::
- Add function that will parse the default MaNGA fits file name (see
:func:`mangadap.config.defaults.manga_fits_root`).
----
.. include license and copyright
.. include:: ../include/copy.rst
----
.. include common links, assuming primary doc root is up one directory
.. include:: ../include/links.rst
"""
from pathlib import Path
from os import environ
import warnings
from IPython import embed
from configparser import ConfigParser, ExtendedInterpolation
[docs]def arginp_to_list(inp, evaluate=False, quiet=True):
"""
Separate a list of comma-separated values in the input string to
a :obj:`list`.
Args:
inp (:obj:`str`, :obj:`list`):
Input string with a list of comma-separated values
evaluate (:obj:`bool`, optional):
Attempt to evaluate the elements in the list using
`eval`_.
quiet (:obj:`bool`, optional):
Suppress terminal output.
Returns:
:obj:`list`: The list of the comma-separated values
"""
out = inp
# Simply return a None value
if out is None:
return out
# If the input value is a string, convert it to a list of strings
if isinstance(out, str):
out = out.replace("[", "")
out = out.replace("]", "")
out = out.replace(" ", "")
out = out.split(',')
# If the input is still not a list, make it one
if not isinstance(out, list):
out = [out]
# Evaluate each string, if requested
if evaluate:
n = len(out)
for i in range(0,n):
try:
tmp = eval(out[i])
except (NameError, TypeError) as e:
if not quiet:
warnings.warn(f'Could not evaluate value {out[i]}. Skipping.')
else:
out[i] = tmp
return out
# TODO: Replace with ', '.join([str(f) for f in flist])
[docs]def list_to_csl_string(flist):
"""
Convert a list to a comma-separated string; i.e. perform the inverse
of :func:`arginp_to_list`.
Args:
flist (:obj:`list`):
List to convert to a comma-separated string
Returns:
:obj:`str`: String with the values in the input list
converted to strings separated by commas
"""
n = len(flist)
out=str(flist[0])
for i in range(1,n):
out += ', '+str(flist[i])
return out
[docs]def parse_drp_file_name(name):
"""
Parse the name of a DRP file to provide the plate, ifudesign, and
mode.
Args:
name (str): Name of the DRP file.
Returns:
int, int, str: The plate, ifudesign, and mode ('RSS' or 'CUBE')
of the DRP file pulled from the file name.
Raises:
TypeError: Raised if *name* is not a string.
ValueError: Raised if if the file name does not look like a DRP
file because it does not include 'manga-', '-LOG', or
'.fits.gz'.
"""
if (type(name) != str):
raise TypeError("File name must be a string.")
if (str.find(name, 'manga-') == -1 or str.find(name, '-LOG') == -1 or
str.find(name, '.fits.gz') == -1):
raise ValueError("String does not look like a DRP fits file name.")
plate_start = str.find(name, '-')+1
plate_end = str.find(name,'-',plate_start)
plate = long(name[plate_start:plate_end])
ifudesign_end = str.find(name,'-',plate_end+1)
ifudesign = long(name[plate_end+1:ifudesign_end])
mode_start = str.find(name,'LOG')+3
mode = name[mode_start:str.find(name,'.fits.gz')]
return plate, ifudesign, mode
#def parse_dap_file_name(name):
# """
# Parse the name of a DAP file and return the plate, ifudesign, mode,
# binning type, and iteration number.
#
# Args:
# name (str): Name of the DAP file.
#
# Returns:
# int, int, str, str, int : The plate, ifudesign, mode ('RSS' or
# 'CUBE'), bin type, and iteration number of the DAP file, pulled
# from the name of the file.
#
# Raises:
# TypeError: Raised if *name* is not a string.
# ValueError: Raised if if the file name does not look like a DRP
# file because it does not include 'manga-', '-BIN', or
# '.fits'.
# """
# if (type(name) != str):
# raise TypeError("File name must be a string.")
# if (str.find(name, 'manga-') == -1 or str.find(name, '-LOG') == -1
# or str.find(name, 'BIN-') == -1 or str.find(name, '.fits') == -1):
# raise ValueError("String does not look like a DAP fits file name.")
#
# plate_start = str.find(name, '-')+1
# plate_end = str.find(name,'-',plate_start)
# plate = int(name[plate_start:plate_end])
#
# ifudesign_end = str.find(name,'-',plate_end+1)
# ifudesign = int(name[plate_end+1:ifudesign_end])
#
# mode_start = str.find(name,'LOG')+3
# mode_end = str.find(name,'_BIN-')
# mode = name[mode_start:mode_end]
#
# bintype_end = str.find(name,'-',mode_end+5)
# bintype = name[mode_end+5:bintype_end]
#
# niter_end = str.find(name,'.fits',bintype_end)
# niter = int(name[bintype_end+1:niter_end])
#
# return plate, ifudesign, mode, bintype, niter
[docs]class DefaultConfig:
"""
A wrapper for the ConfigParser class that handles None values and
provides some convenience functions.
"""
def __init__(self, f=None, interpolate=False):
# TODO: May not need extended interpolation anymore...
self.cnfg = ConfigParser(environ, allow_no_value=True,
interpolation=ExtendedInterpolation()) \
if interpolate else ConfigParser(allow_no_value=True)
if f is None:
return
self.read(f)
def __getitem__(self, key):
return self.cnfg['default'][key]
def __iter__(self):
return self.cnfg.options('default').__iter__()
[docs] def read(self, f):
_f = Path(f).resolve()
if not _f.exists():
raise FileNotFoundError(f'Configuration file not found: {_f}')
self.cnfg.read(f)
[docs] def keyword_specified(self, key):
return key in self and self[key] is not None
[docs] def all_required(self, keys):
for k in keys:
if k not in self or self[k] is None:
return False
return True
[docs] def get(self, key, default=None):
return default if key not in self or self.cnfg['default'][key] is None \
else self.cnfg['default'][key]
[docs] def getint(self, key, default=None):
return default if key not in self or self.cnfg['default'][key] is None \
else self.cnfg['default'].getint(key)
[docs] def getbool(self, key, default=None):
return default if key not in self or self.cnfg['default'][key] is None \
else self.cnfg['default'].getboolean(key)
[docs] def getfloat(self, key, default=None):
return default if key not in self or self.cnfg['default'][key] is None \
else self.cnfg['default'].getfloat(key)
[docs] def getlist(self, key, evaluate=False, default=None):
if key not in self or self.cnfg['default'][key] is None:
return default
return [ eval(e.strip()) if evaluate else e.strip() \
for e in self.cnfg['default'][key].split(',') ]