# -*- coding: utf-8 -*-
# This file is part of AudioLazy, the signal processing Python package.
# Copyright (C) 2012-2016 Danilo de Jesus da Silva Bellini
#
# AudioLazy 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, version 3 of the License.
#
# 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/>.
"""
Strings, reStructuredText, docstrings and other general text processing
"""
from __future__ import division
import itertools as it
from fractions import Fraction
# Audiolazy internal imports
from .lazy_compat import xzip, xzip_longest, iteritems
from .lazy_misc import rint, elementwise, blocks
from .lazy_core import StrategyDict
from .lazy_math import pi
__all__ = ["multiplication_formatter", "pair_strings_sum_formatter",
"float_str", "rst_table", "small_doc", "format_docstring"]
float_str = StrategyDict("float_str")
float_str.__class__.pi_symbol = r"$\pi$"
float_str.__class__.pi_value = pi
@float_str.strategy("auto")
def float_str(value, order="pprpr", size=[4, 5, 3, 6, 4],
after=False, max_denominator=1000000):
"""
Pretty string from int/float.
"Almost" automatic string formatter for integer fractions, fractions of
:math:`\pi` and float numbers with small number of digits.
Outputs a representation among ``float_str.pi``, ``float_str.frac`` (without
a symbol) strategies, as well as the usual float representation. The
formatter is chosen by counting the resulting length, trying each one in the
given ``order`` until one gets at most the given ``size`` limit parameter as
its length.
Parameters
----------
value :
A float number or an iterable with floats.
order :
A string that gives the order to try formatting. Each char should be:
- ``"p"`` for pi formatter (``float_str.pi``);
- ``"r"`` for ratio without symbol (``float_str.frac``);
- ``"f"`` for the float usual base 10 decimal representation.
Defaults to ``"pprpr"``. If no trial has the desired size, returns the
float representation.
size :
The max size allowed for each formatting in the ``order``, respectively.
Defaults to ``[4, 5, 3, 6, 4]``.
after :
Chooses the place where the :math:`\pi` symbol should appear, when such
formatter apply. If ``True``, that's the end of the string. If ``False``,
that's in between the numerator and the denominator, before the slash.
Defaults to ``False``.
max_denominator :
The data in ``value`` is rounded following the limit given by this
parameter when trying to represent it as a fraction/ratio.
Defaults to the integer 1,000,000 (one million).
Returns
-------
A string with the number written into.
Note
----
You probably want to keep ``max_denominator`` high to avoid rounding.
"""
if len(order) != len(size):
raise ValueError("Arguments 'order' and 'size' should have the same size")
str_data = {
"p": float_str.pi(value, after=after, max_denominator=max_denominator),
"r": float_str.frac(value, max_denominator=max_denominator),
"f": elementwise("v", 0)(lambda v: "{0:g}".format(v))(value)
}
sizes = {k: len(v) for k, v in iteritems(str_data)}
sizes["p"] = max(1, sizes["p"] - len(float_str.pi_symbol) + 1)
for char, max_size in xzip(order, size):
if sizes[char] <= max_size:
return str_data[char]
return str_data["f"]
@float_str.strategy("frac", "fraction", "ratio", "rational")
@elementwise("value", 0)
def float_str(value, symbol_str="", symbol_value=1, after=False,
max_denominator=1000000):
"""
Pretty rational string from float numbers.
Converts a given numeric value to a string based on rational fractions of
the given symbol, useful for labels in plots.
Parameters
----------
value :
A float number or an iterable with floats.
symbol_str :
String data that will be in the output representing the data as a
numerator multiplier, if needed. Defaults to an empty string.
symbol_value :
The conversion value for the given symbol (e.g. pi = 3.1415...). Defaults
to one (no effect).
after :
Chooses the place where the ``symbol_str`` should be written. If ``True``,
that's the end of the string. If ``False``, that's in between the
numerator and the denominator, before the slash. Defaults to ``False``.
max_denominator :
An int instance, used to round the float following the given limit.
Defaults to the integer 1,000,000 (one million).
Returns
-------
A string with the rational number written into as a fraction, with or
without a multiplying symbol.
Examples
--------
>>> float_str.frac(12.5)
'25/2'
>>> float_str.frac(0.333333333333333)
'1/3'
>>> float_str.frac(0.333)
'333/1000'
>>> float_str.frac(0.333, max_denominator=100)
'1/3'
>>> float_str.frac(0.125, symbol_str="steps")
'steps/8'
>>> float_str.frac(0.125, symbol_str=" Hz",
... after=True) # The symbol includes whitespace!
'1/8 Hz'
See Also
--------
float_str.pi :
This fraction/ratio formatter, but configured with the "pi" symbol.
"""
if value == 0:
return "0"
frac = Fraction(value/symbol_value).limit_denominator(max_denominator)
num, den = frac.numerator, frac.denominator
output_data = []
if num < 0:
num = -num
output_data.append("-")
if (num != 1) or (symbol_str == "") or after:
output_data.append(str(num))
if (value != 0) and not after:
output_data.append(symbol_str)
if den != 1:
output_data.extend(["/", str(den)])
if after:
output_data.append(symbol_str)
return "".join(output_data)
@float_str.strategy("pi")
def float_str(value, after=False, max_denominator=1000000):
"""
String formatter for fractions of :math:`\pi`.
Alike the rational_formatter, but fixed to the symbol string
``float_str.pi_symbol`` and value ``float_str.pi_value`` (both can be
changed, if needed), mainly intended for direct use with MatPlotLib labels.
Examples
--------
>>> float_str.pi_symbol = "pi" # Just for printing sake
>>> float_str.pi(pi / 2)
'pi/2'
>>> float_str.pi(pi * .333333333333333)
'pi/3'
>>> float_str.pi(pi * .222222222222222)
'2pi/9'
>>> float_str.pi_symbol = " PI" # With the space
>>> float_str.pi(pi / 2, after=True)
'1/2 PI'
>>> float_str.pi(pi * .333333333333333, after=True)
'1/3 PI'
>>> float_str.pi(pi * .222222222222222, after=True)
'2/9 PI'
See Also
--------
float_str.frac :
Float to string conversion, perhaps with a symbol as a multiplier.
"""
return float_str.frac(value, symbol_str=float_str.pi_symbol,
symbol_value=float_str.pi_value, after=after,
max_denominator=max_denominator)
[docs]def rst_table(data, schema=None):
"""
Creates a reStructuredText simple table (list of strings) from a list of
lists.
"""
# Process multi-rows (replaced by rows with empty columns when needed)
pdata = []
for row in data:
prow = [el if isinstance(el, list) else [el] for el in row]
pdata.extend(pr for pr in xzip_longest(*prow, fillvalue=""))
# Find the columns sizes
sizes = [max(len("{0}".format(el)) for el in column)
for column in xzip(*pdata)]
sizes = [max(size, len(sch)) for size, sch in xzip(sizes, schema)]
# Creates the title and border rows
if schema is None:
schema = pdata[0]
pdata = pdata[1:]
border = " ".join("=" * size for size in sizes)
titles = " ".join("{1:^{0}}".format(*pair)
for pair in xzip(sizes, schema))
# Creates the full table and returns
rows = [border, titles, border]
rows.extend(" ".join("{1:<{0}}".format(*pair)
for pair in xzip(sizes, row))
for row in pdata)
rows.append(border)
return rows
[docs]def small_doc(obj, indent="", max_width=80):
"""
Finds a useful small doc representation of an object.
Parameters
----------
obj :
Any object, which the documentation representation should be taken from.
indent :
Result indentation string to be insert in front of all lines.
max_width :
Each line of the result may have at most this length.
Returns
-------
For classes, modules, functions, methods, properties and StrategyDict
instances, returns the first paragraph in the doctring of the given object,
as a list of strings, stripped at right and with indent at left.
For other inputs, it will use themselves cast to string as their docstring.
"""
if not getattr(obj, "__doc__", False):
data = [el.strip() for el in str(obj).splitlines()]
if len(data) == 1:
if data[0].startswith("<audiolazy.lazy_"): # Instance
data = data[0].split("0x", -1)[0] + "0x...>" # Hide its address
else:
data = "".join(["``", data[0], "``"])
else:
data = " ".join(data)
# No docstring
elif (not obj.__doc__) or (obj.__doc__.strip() == ""):
data = "\ * * * * ...no docstring... * * * * \ "
# Docstring
else:
data = (el.strip() for el in obj.__doc__.strip().splitlines())
data = " ".join(it.takewhile(lambda el: el != "", data))
# Ensure max_width (word wrap)
max_width -= len(indent)
result = []
for word in data.split():
if len(word) <= max_width:
if result:
if len(result[-1]) + len(word) + 1 <= max_width:
word = " ".join([result.pop(), word])
result.append(word)
else:
result = [word]
else: # Splits big words
result.extend("".join(w) for w in blocks(word, max_width, padval=""))
# Apply indentation and finishes
return [indent + el for el in result]