# -*- coding: utf-8 -*-
"""
=========================================================================
Helper functions for string manipulation (:mod:`sknano.core.strings`)
=========================================================================
.. currentmodule:: sknano.core.strings
"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'
from textwrap import shorten
from tabulate import tabulate
from pyparsing import Group, Forward, Optional, Regex, Suppress, Keyword, \
Literal, Word, ZeroOrMore, alphas, alphanums, delimitedList, \
oneOf, replaceWith, quotedString, removeQuotes
__all__ = ['TabulateMixin', 'obj_mro_str', 'pluralize', 'plural_word_check',
'ordinal_form', 'map_operator', 'map_function', 'asint', 'asfloat',
'asbool', 'astuple', 'aslist', 'asdict', 'asset', 'integer', 'real',
'number', 'boolean', 'string', 'none', 'LPAR', 'RPAR',
'LBRACK', 'RBRACK', 'LBRACE', 'RBRACE', 'COLON', 'SEMICOLON',
'SPACE', 'COMMA', 'EQUAL', 'binary_operator', 'hashable_item',
'unhashable_item', 'expr_item', 'tuple_expr', 'list_expr',
'dict_expr', 'set_expr', 'kwarg', 'kwargs_expr', 'signature_args',
'signature_kwargs', 'call_signature']
[docs]class TabulateMixin:
"""Mixin class for pretty tabulated output strings."""
def __str__(self):
title = self._table_title_str()
table = self._tabulate()
return '\n'.join((title, table))
def _tabulate(self, values=None, headers=(), tablefmt='fancy_grid'):
if values is None:
return self._tabulate(*self._tabular_data())
return tabulate(values, headers=headers, tablefmt=tablefmt)
def _tabular_data(self):
"""Return :class:`~python:tuple` of tabular data for \
pretty tabulated output."""
header = self.__class__.__name__
fmt = self._tabular_data_format_string
return [fmt(self)], (header,)
def _table_title_str(self, prepend='', obj=None, append='', sep=''):
if obj is None:
title = repr(self)
elif obj is not None and not isinstance(obj, str):
title = repr(obj)
title = '{}'.format(shorten(title, width=78))
return sep.join((prepend, title, append))
def _tabular_data_format_string(self, value, begin=None, end=None):
strrep = '{!r}'.format(value)
try:
return strrep[begin:end]
except IndexError:
return strrep
def _obj_mro_str(self):
return obj_mro_str(self.__class__)
[docs]def obj_mro_str(obj, include_obj_name=True, sep='.'):
"""Helper function to return a string of `obj` base classes."""
obj = obj if hasattr(obj, '__name__') else obj.__class__
bases = obj.__mro__
mro_str = obj.__name__
for base in bases:
name = base.__name__
if name.endswith('Mixin') or \
name in ('BaseClass', 'UserList', 'object', 'type'):
break
mro_str = sep.join((name, mro_str))
return mro_str
[docs]def pluralize(word, count):
"""Make a word plural by adding an *s* if `count` != 1.
Parameters
----------
word : :class:`~python:str`
the word
count : :class:`~python:int`
the word count
Returns
-------
:class:`~python:str`
Examples
--------
On occasion, it is desirable to describe a numerical value in terms of
a noun which the number is quantifying. For example, given
a function which accepts a numerical parameter `n` and returns
a string describing the number of `n` *objects*, then this
helper function may be of use. For example::
>>> from sknano.core import pluralize
>>> def apple_count(n):
... return '{} {}'.format(n, pluralize('apple', n))
...
>>> [apple_count(i) for i in range(3)]
['0 apples', '1 apple', '2 apples']
"""
return word if count == 1 else word + 's'
[docs]def plural_word_check(word, count):
"""Alias for :func:`pluralize`"""
return pluralize(word, count)
type_map = {
'bool': bool,
'int': int,
'float': float,
'list': list,
'str': str,
'tuple': tuple,
}
def map_type_str(type_str):
return type_map[type_str]
def cast_type(parameters):
pass
[docs]def asint(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:int`."""
return int(t[0])
[docs]def asfloat(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:float`."""
try:
val = int(t[0])
except ValueError:
val = float(t[0])
return val
[docs]def asbool(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:bool`."""
return t[0] == 'True'
[docs]def astuple(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:tuple`."""
return tuple(t.asList())
[docs]def aslist(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:list`."""
return [t.asList()]
[docs]def asdict(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:dict`."""
return dict(t.asList())
[docs]def asset(s, l, t):
""":mod:`pyparsing` parse action to convert token to \
:class:`~python:set`."""
return set(t.asList())
[docs]def map_function(s, l, t):
""":mod:`pyparsing` parse action to convert function string to \
function in :func:`sknano.core.math.function_map`."""
from sknano.core.math import function_map
return function_map[t[0]]
[docs]def map_operator(s, l, t):
""":mod:`pyparsing` parse action to convert operator string to \
operator in :func:`sknano.core.math.operator_map`."""
from sknano.core.math import operator_map
return operator_map[t[0]]
integer = Regex(r'[+-]?\d+').setParseAction(asint)
real = Regex(r'[+-]?\d+(\.\d*)?([eE][+-]?\d+)?').setParseAction(asfloat)
number = real | integer
boolean = oneOf("True False").setParseAction(asbool)
none = Literal("None").setParseAction(replaceWith(None))
string = quotedString.setParseAction(removeQuotes)
# point = Literal('.')
# e = CaselessLiteral('E')
# plus_or_minus_sign = Word('+-', exact=1)
# number = Word(nums)
# positive_integer = number.setParseAction(asint)
# real_number = \
# Combine(Optional(plus_or_minus_sign) +
# (number + point + Optional(number) | (point + number)) +
# Optional(e) + Optional(plus_or_minus_sign) + number) \
# .setParseAction(asfloat)
LPAR, RPAR = map(Suppress, '()')
LBRACK, RBRACK = map(Suppress, '[]')
LBRACE, RBRACE = map(Suppress, '{}')
COLON = Suppress(':')
SEMICOLON = Suppress(';')
SPACE = Suppress(' ')
COMMA = Suppress(',')
EQUAL = Suppress('=')
binary_operator = oneOf('< <= == > >= != LT LE EQ GT GE NE', caseless=True)
binary_operator.setParseAction(map_operator)
tuple_expr = Forward()
list_expr = Forward()
dict_expr = Forward()
set_expr = Forward()
hashable_item = real | integer | string | boolean | none | tuple_expr
unhashable_item = list_expr | dict_expr | set_expr
expr_item = hashable_item | unhashable_item
tuple_expr << (LPAR +
Optional(delimitedList(expr_item) + Optional(COMMA)) +
RPAR).setParseAction(astuple)
list_expr << (LBRACK +
Optional(delimitedList(expr_item) + Optional(COMMA)) +
RBRACK).setParseAction(aslist)
kwarg = Group(Word(alphas, alphanums + '_') + EQUAL + expr_item)
kwargs_expr = Forward()
kwargs_expr << delimitedList(kwarg).setParseAction(asdict)
dict_entry = Group(hashable_item + COLON + expr_item) | kwarg
dict_entries = Optional(delimitedList(dict_entry) + Optional(COMMA))
dict_expr << (LBRACE + dict_entries + RBRACE |
Suppress(Keyword('dict')) + LPAR +
dict_entries + RPAR).setParseAction(asdict)
signature_args = Forward()
signature_args << delimitedList(expr_item).setParseAction(aslist)
signature_kwargs = kwargs_expr.copy()
call_signature = Group(Optional(signature_args + COMMA) +
ZeroOrMore(signature_kwargs))