# Virus Data Analysis Tool: a data reduction GUI for HETDEX/VIRUS data
# Copyright (C) 2015, 2016 "The HETDEX collaboration"
#
# 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 <https://www.gnu.org/licenses/>.
"""Utility functionalities
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import abc
import functools
import re
import six
from six.moves import range
from vdat.command_interpreter import exceptions
[docs]def id_(*args):
"""Identity function: returns the input unchanged
If the input is one element only, extract it from ``args``
Examples
--------
>>> id_()
()
>>> id_(5)
5
>>> id_(3, 4)
(3, 4)
>>> a, b, c = id_(2, 3, 4)
>>> print(a, b, c)
2 3 4
"""
if len(args) == 1:
return args[0]
else:
return args
[docs]def flip(func):
"""Flip the order of the arguments in the function
Can also be used a decorator
Parameters
----------
func : callable
function to replace
Returns
-------
callable
"""
@functools.wraps(func)
def wrap(*args, **kwargs):
return func(*args[::-1], **kwargs)
return wrap
[docs]def multiwrap(*decorators):
"""Create a decorator combining the given decorators"""
def wrapper(func):
for d in decorators[::-1]:
func = d(func)
return func
return wrapper
[docs]class SliceLike(object):
"""A slice like object, but it's not usable as :class:`slice`.
If the input argument is a string of ``:`` or ``,`` separated integers
parse it assuming ``[start]:[stop][:step]``. ``start``, ``stop``, and
``step`` must be integers or ``None``; the latter is optional and cannot be
zero.
It's possible to use the ``in`` statement to check if a value is in the
slice with the following logic:
* if ``start`` is not given, it checks that the value is less than ``stop``
* if ``stop`` is not given, it checks that the value is greater or equal
than ``start``
* if neither ``start`` not ``stop`` are given and ``step`` is not given or
one, returns ``True``
* if ``step`` is not 1, it checks that the value is equal to ``start +
n*step`` with integer ``n``.
* if ``start`` is not given and ``step`` is not 1, returns ``False``;
undefined behaviour.
Parameters
----------
str_or_int, args : string or one/two/three integers
if the first argument is a string is parsed as if it's a ``:`` or ``,``
separated one and all the elements are converted to None (if empty) or
to integers. In this case no other arguments are allowed.
If the arguments are integers are interpreted as:
``SliceLike(stop)``
``SliceLike(start, stop[, step])``
Raises
------
CISliceError
if the initialisation fails
Attributes
----------
start
stop
step
"""
def __init__(self, str_or_int, *args):
if isinstance(str_or_int, six.string_types) and not args:
str_ = [i.strip() for i in re.split(r'[:,]', str_or_int,
maxsplit=2)]
if len(str_) < 2:
msg = ("At least one `:` or `,` must be present in the input"
" string")
raise exceptions.CISliceError(msg)
str_ = (str_ + [None, ])[:3] # make sure is three elements
try:
self._start = int(str_[0]) if str_[0] else None
self._stop = int(str_[1]) if str_[1] else None
self._step = int(str_[2]) if str_[2] else None
except ValueError as e:
msg = ("It's not possible to convert the input string to"
"integers because of: {}")
raise exceptions.CISliceError(msg.format(e))
elif all(isinstance(i, (int, type(None))) for i in [str_or_int, ] +
list(args)) and len(args) <= 2:
self._start = None
self._stop = str_or_int
self._step = None
if args:
self._start = self._stop
self._stop = args[0]
if len(args) == 2:
self._step = args[1]
else:
raise exceptions.CISliceError('The input must be one single string'
' or one to three integers')
if self._step == 0:
raise exceptions.CISliceError('The step size cannot be zero')
@property
def start(self):
"Start of the slice (read only)"
return self._start
@property
def stop(self):
"Stop of the slice (read only)"
return self._stop
@property
def step(self):
"Step of the slice (read only)"
return self._step
def __contains__(self, item):
"""item in the slice"""
_greater = item >= self.start if self.start else True
_less = item < self.stop if self.stop else True
_step = False
if _greater and _less:
if not self.step or self.step == 1:
_step = True
elif self.step >= 2:
if self.start:
n = (item - self.start) // self.step
_step = item == self.start + n * self.step
return _greater and _less and _step
def __str__(self):
return "{}({}, {}, {})".format(self.__class__.__name__, self.start,
self.stop, self.step)
def __repr__(self):
return self.__str__()
def __format__(self, format_spec):
return self.__str__()
[docs] def range(self):
"""Create and returns a range using the elements of the slice.
If :attr:`start` is ``None``, set it to 0, if :attr:`stop` is ``None``,
it fails and when :attr:`step` is ``None`` set it to 1.
Returns
-------
a range or a xrange object (python 3 / python 2)
"""
return range(self.start or 0, self.stop, self.step or 1)
if six.PY2:
abstractproperty = abc.abstractproperty
else:
abstractproperty = multiwrap(property, abc.abstractmethod)
"""* Python 3: decorator from combining :class:`property` and
:func:`abc.abstractmethod`
* Python 2: alias of :func:`abc.abstractproperty`
"""