# GromacsWrapper: xpm.py
# Copyright (c) 2012 Oliver Beckstein <orbeckst@gmail.com>
# Released under the GNU Public License 3 (or higher, your choice)
# See the file COPYING for details.
"""
:mod:`gromacs.fileformats.convert` --- converting entries of tables
===================================================================
The :class:`Autoconverter` converts input values to appropriate Python
types.
It is mainly used by :class:`gromacs.fileformats.xpm.XPM`
to automagically generate useful NumPy arrays from xpm files. Custom
conversions beyond the default ones in :class:`Autoconverter` can be
provided with the constructor keyword *mapping*.
.. SeeAlso::
The :class:`Autoconverter` class was taken and slightly adapted from
`recsql.converter` in RecSQL_.
.. _RecSQL: http://orbeckst.github.com/RecSQL/
.. autoclass:: Autoconverter
:members:
.. function:: convert(x)
Convert *x* (if in the active state)
.. attribute:: active
If set to ``True`` then conversion takes place; ``False`` just
returns :func:`besttype` applid to the value.
.. autofunction:: besttype
.. autofunction:: to_unicode
"""
import re
[docs]
def to_unicode(obj):
"""Convert obj to unicode (if it can be be converted).
Conversion is only attempted if `obj` is a string type (as
determined by :class:`str`).
.. versionchanged:: 0.7.0
removed *encoding* keyword argument
"""
if not isinstance(obj, str):
return obj
try:
obj = str(obj)
except TypeError:
pass
return obj
[docs]
class Autoconverter(object):
"""Automatically convert an input value to a special python object.
The :meth:`Autoconverter.convert` method turns the value into a special
python value and casts strings to the "best" type (see :func:`besttype`).
The defaults for the conversion of a input field value to a
special python value are:
=========== ===============
value python
=========== ===============
'``---``' ``None``
'' ``None``
'True' ``True``
'x' ``True``
'X' ``True``
'yes' ``True``
'Present' ``True``
'False' ``False``
'-' ``False``
'no' ``False``
'None' ``False``
'none' ``False``
=========== ===============
If the *sep* keyword is set to a string instead of ``False`` then
values are split into tuples. Probably the most convenient way to
use this is to set *sep* = ``True`` (or ``None``) because this
splits on all white space whereas *sep* = ' ' would split multiple
spaces.
**Example**
- With *sep* = ``True``: 'foo bar 22 boing ``---``' --> ('foo', 'bar', 22, 'boing', None)
- With *sep* = ',': 1,2,3,4 --> (1,2,3,4)
"""
def __init__(self, mode="fancy", mapping=None, active=True, sep=False, **kwargs):
"""Initialize the converter.
:Arguments:
*mode*
defines what the converter does
"simple"
convert entries with :func:`besttype`
"singlet"
convert entries with :func:`besttype` and apply
mappings
"fancy"
first splits fields into lists, tries mappings,
and does the stuff that "singlet" does
"unicode"
convert all entries with :func:`to_unicode`
*mapping*
any dict-like mapping that supports lookup. If``None`` then the
hard-coded defaults are used
*active* or *autoconvert*
initial state of the :attr:`Autoconverter.active` toggle.
``False`` deactivates any conversion. [``True``]
*sep*
character to split on (produces lists); use ``True`` or ``None``
(!) to split on all white space.
.. versionchanged:: 0.7.0
removed *encoding* keyword argument
"""
self._convertors = {
"unicode": to_unicode,
"simple": besttype,
"singlet": self._convert_singlet,
"fancy": self._convert_fancy,
}
self.convert = None # convertor function; set when self.active <-- True.
if mapping is None:
mapping = {
"---": None,
"": None,
"True": True,
"x": True,
"X": True,
"yes": True,
"Present": True,
"present": True,
"False": False,
"no": False,
"-": False,
"None": False,
"none": False,
}
self.mapping = mapping
self.mode = mode
self.__active = None
self.active = kwargs.pop(
"autoconvert", active
) # 'autoconvert' is a "strong" alias of 'active'
if sep is True:
sep = None # split on *all* white space, sep=' ' splits single spaces!
self.sep = sep
def active():
doc = """Toggle the state of the Autoconverter. ``True`` uses the mode, ``False`` does nothing"""
def fget(self):
return self.__active
def fset(self, x):
self.__active = x
if self.__active:
self.convert = self._convertors[self.mode]
else:
self.convert = lambda x: x # do nothing
return locals()
active = property(**active())
def _convert_singlet(self, s):
x = besttype(s)
try:
return self.mapping[x]
except KeyError:
return x
def _convert_fancy(self, field):
"""Convert to a list (sep != None) and convert list elements."""
if self.sep is False:
x = self._convert_singlet(field)
else:
x = tuple([self._convert_singlet(s) for s in field.split(self.sep)])
if len(x) == 0:
x = ""
elif len(x) == 1:
x = x[0]
# print "%r --> %r" % (field, x)
return x
[docs]
def besttype(x):
"""Convert string x to the most useful type, i.e. int, float or unicode string.
If x is a quoted string (single or double quotes) then the quotes
are stripped and the enclosed string returned.
.. Note::
Strings will be returned as Unicode strings (using :func:`to_unicode`).
.. versionchanged:: 0.7.0
removed *encoding* keyword argument
"""
x = to_unicode(x) # make unicode as soon as possible
try:
x = x.strip()
except AttributeError:
pass
m = re.match(r"""['"](?P<value>.*)["']$""", x)
if m is None:
# not a quoted string, try different types
for converter in (
int,
float,
to_unicode,
): # try them in increasing order of lenience
try:
return converter(x)
except ValueError:
pass
else:
# quoted string
x = to_unicode(m.group("value"))
return x
def to_int64(a):
"""Return view of the recarray with all int32 cast to int64."""
# build new dtype and replace i4 --> i8
def promote_i4(typestr):
if typestr[1:] == "i4":
typestr = typestr[0] + "i8"
return typestr
dtype = [(name, promote_i4(typestr)) for name, typestr in a.dtype.descr]
return a.astype(dtype)
def pyify(typestr):
if typestr[1] in "iu":
return int
elif typestr[1] == "f":
return float
elif typestr[1] == "S":
return str
return lambda x: x
def to_pytypes(a):
dtype = [(name, pyify(typestr)) for name, typestr in a.dtype.descr]
return a.astype(dtype)
def irecarray_to_py(a):
"""Slow conversion of a recarray into a list of records with python types.
Get the field names from :attr:`a.dtype.names`.
:Returns: iterator so that one can handle big input arrays
"""
pytypes = [pyify(typestr) for name, typestr in a.dtype.descr]
def convert_record(r):
return tuple([converter(value) for converter, value in zip(pytypes, r)])
return (convert_record(r) for r in a)