Source code for pyyeti.writer
# -*- coding: utf-8 -*-
"""
Collection of tools for writing formatted text to files.
"""
import numpy as np
from pyyeti import guitools
[docs]
def getith(i, args, fncs):
"""
Return list with i'th value from each input, typically called by
:func:`vecwrite`.
Parameters
----------
i : integer
Specifies which value to extract from each input; starts at 0.
args : list of variables
Variable to extract the i'th value from. Must be compatibly
sized (scalars or vectors of equal length). Strings are
considered scalars.
fncs : list of functions
Same length as args; the function is used to extract the
i'th item. Call signature: ith_element_of_a = func(a, i).
The function must return an iterable of items (eg, list).
Returns
-------
lst : list
List of the i'th items extracted from each variable in `args`.
Examples
--------
>>> from pyyeti import writer
>>> import numpy as np
>>> r = np.array([1.2, 45.])
>>> s = 'test string'
>>> i = 5
>>> v = ['One', 'Two']
>>> def f(a, i): return [a]
>>> def f2(a, i): return [a[i]]
>>> args = [r, s, i, v]
>>> fncs = [f2, f, f, f2]
>>> writer.getith(0, args, fncs)
[1.2, 'test string', 5, 'One']
>>> writer.getith(1, args, fncs)
[45.0, 'test string', 5, 'Two']
"""
lst = []
for arg, fnc in zip(args, fncs):
lst.extend(fnc(arg, i))
return lst
@guitools.write_text_file
def _vecwrite(fout, string, length, args, fncs, postfunc, pfargs, so):
"""Utility routine for :func:`vecwrite`."""
v = range(length)
if so is not None:
v = v[so]
if postfunc:
if pfargs is None:
pfargs = []
for i in v:
curargs = getith(i, args, fncs)
s = postfunc(string.format(*curargs), *pfargs)
fout.write(s)
else:
for i in v:
curargs = getith(i, args, fncs)
fout.write(string.format(*curargs))
[docs]
def vecwrite(f, string, *args, postfunc=None, pfargs=None, so=None):
"""
Vectorized write.
Parameters
----------
f : string or file_like or 1 or None
Either a name of a file, or is a file_like object as returned
by :func:`open` or :class:`io.StringIO`. Input as integer 1 to
write to stdout. Can also be the name of a directory or None;
in these cases, a GUI is opened for file selection.
string : string
The formatting string for the write, Python 3 format as in:
`string`.format(a,b)
*args : list of variables
Variables to write. Must be compatibly sized (scalars or
vectors or numpy arrays of compatible sizes). numpy arrays of
length 1 are considered scalars. For 2-d numpy arrays, each
row is written on one line and each element of the row must
have a conversion specifier. 1-d numpy arrays are treated
like a column 2-d numpy array. Strings are considered
scalars.
postfunc : function or None
If a function, it is called with the final string (for each
line) as the argument and it must return a string. The return
string is what gets output. This can be handy for final string
substitutions, for example. This input must be named and must
be after the arguments to be printed; see example.
pfargs : iterable or None
If an iterable, contains extra arguments to pass to `postfunc`
after the string argument. Must be named and after the
arguments to be printed.
so : slice object or None
Allows selection of limited range and custom increment; eg:
``slice(0, 10, 2)``. Scalars are not sliced. Must be named and
after the arguments to be printed.
Returns
-------
None.
Notes
-----
The expected vector length is determined from the first non-scalar
input. Note that scalar values are repeated automatically as
necessary.
Raises
------
ValueError
When the lengths of print arguments do not match (for
lengths > 1). Note that the slice object `so` can make
otherwise incompatible arguments compatible; for example,
arguments of length 10 and length 100 would be compatible if
``so = slice(10)`` (or similar).
Examples
--------
>>> from pyyeti import writer
>>> import sys
>>> import numpy as np
>>> r = np.array([1.2, 45.8])
>>> s = 'test string'
>>> i = 5
>>> v = ['short string', 'a bit longer string']
>>> frm = '{:3}, {:5.1f}, {:<25}, {}' + chr(10)
>>> writer.vecwrite(sys.stdout, frm, i, r, v, s)
5, 1.2, short string , test string
5, 45.8, a bit longer string , test string
>>> r = np.array([[1.1, 1.2, 1.3], [10.1, 10.2, 10.3]])
>>> frm = '{:2}, {:=^25} : ' + ' {:6.2f}'*3 + chr(10)
>>> writer.vecwrite(sys.stdout, frm, i, v, r)
5, ======short string======= : 1.10 1.20 1.30
5, ===a bit longer string=== : 10.10 10.20 10.30
>>> def pf(s):
... return s.replace('0 ', ' ')
>>> writer.vecwrite(sys.stdout, frm, i, v, r, postfunc=pf)
5, ======short string======= : 1.1 1.2 1.30
5, ===a bit longer string=== : 10.1 10.2 10.30
>>> def pf(s, s_old, s_new):
... return s.replace(s_old, s_new)
>>> writer.vecwrite(1, frm, i, v, r, postfunc=pf,
... pfargs=['0 ', ' '])
5, ======short string======= : 1.1 1.2 1.30
5, ===a bit longer string=== : 10.1 10.2 10.30
"""
def _get_scalar(a, i):
return [a]
def _get_scalar1(a, i):
return [a[0]]
def _get_itemi(a, i):
return [a[i]]
def _get_matrow(a, i):
return a[i]
length = 1
fncs = []
for i, arg in enumerate(args):
if not isinstance(arg, str) and hasattr(arg, "__len__"):
if np.ndim(arg) == 2:
fncs.append(_get_matrow)
curlen = np.size(arg, 0)
elif len(arg) == 1:
fncs.append(_get_scalar1)
curlen = 1
else:
fncs.append(_get_itemi)
curlen = len(arg)
if curlen > 1:
if length > 1:
if so is not None:
if range(curlen)[so] != range(length)[so]:
msg = (
"length mismatch with slice object:"
f" arg # {i + 1} is incompatible with "
"previous args"
)
raise ValueError(msg)
elif curlen != length:
msg = (
f"length mismatch: arg # {i + 1} has "
f"length {curlen}; expected {length} or 1."
)
raise ValueError(msg)
length = curlen
else:
fncs.append(_get_scalar)
_vecwrite(f, string, length, args, fncs, postfunc, pfargs, so)
[docs]
def formheader(headers, widths, formats, sep=(0, 2), just=-1, ulchar="-"):
"""
Form a nice table header for formatted output via f.write().
Parameters
----------
headers : list or tuple
List or tuple of column header strings, eg:
['Desc', 'Maximum', 'Time']. Can also be a list of lists (or
tuples) to support multiple header lines, eg:
[['Maximum', 'Minimum', 'Time'], ['(lbs)', '(lbs)', '(sec)']]
widths : iterable
Iterable of field widths, eg: (25, 10, 8) or [25, 10, 8]. If
an element in `widths` is < length of corresponding word in a
header-line, the length of the word is used for that field.
Note that if this doesn't match with `formats`, the columns
will not line up nicely.
formats : list or tuple
List or tuple of format specifiers for the values in the table,
eg: ['{:25s}', '{:10f}', '{:8.3f}']
sep : string, list, tuple, or integer
Defines 'spacer' in front of each word:
- if a string, that string is used in front of all headers
- use a list or tuple of strings for complete control
- if an integer, that many spaces are used in front of all
headers
- use a vector of integers to specify a variable number of
spaces
- if len(sep) < len(headers), the last element is used for
all remaining elements
just : string or integer or list
Justification flag or flags for each header string:
- 'l', 'c', 'r' (or -1, 0, 1) to left, center, or right
justify headers in their fields
- can be a list or tuple of len(headers) for complete
control
ulchar : string
Character to use for underlining of headers.
Returns
-------
hu : string
Contains formatted header string(s) and the underline string.
f : string
Final formatting string.
Examples
--------
>>> import numpy as np
>>> import sys
>>> from pyyeti import writer
>>> descs = ['Item 1', 'A different item']
>>> mx = np.array([[1.2, 2.3], [3.4, 4.5]]) * 1000
>>> time = np.array([[1.234], [2.345]])
>>> headers = [['The']*3, ['Descriptions', 'Maximum', 'Time']]
>>> formats = ['{:<25s}', '{:10.2f}', '{:8.3f}']
>>> widths = [25, 10, 8]
>>> hu, f = writer.formheader(headers, widths, formats,
... sep=[4, 5, 2], just=0)
>>> fout = sys.stdout
>>> if 1: # just so all output is together
... b = fout.write(hu)
... writer.vecwrite(fout, f, descs, mx, time)
The The The
Descriptions Maximum Time
------------------------- ---------- --------
Item 1 1200.00 2300.000
A different item 3400.00 4500.000
"""
if not isinstance(headers, (list, tuple)):
raise ValueError("input 'headers' must be a list or tuple")
if isinstance(headers[0], (list, tuple)):
length = len(headers[0])
nheaders = len(headers)
mxlengths = np.array([len(s) for s in headers[0]])
for j in range(1, nheaders):
if len(headers[j]) != length:
raise ValueError(
f"headers[{len(headers[j])}] != length of previous headers"
)
for k in range(length):
mxlengths[k] = max(mxlengths[k], len(headers[j][k]))
else:
nheaders = 0
mxlengths = np.array([len(s) for s in headers])
length = len(headers)
if not length == len(formats) == len(widths):
s = ""
if isinstance(headers[0], (list, tuple)):
s = "[*]"
raise ValueError(
f"this check failed: ``len(headers{s}) == len(formats) == len(widths)``"
)
def strexp(string, width, just):
if just == -1 or just == "l":
return string.ljust(width)
if just == 0 or just == "c":
return string.center(width)
return string.rjust(width)
if isinstance(just, (str, int)):
just = [just]
if isinstance(sep, int):
sep = " " * sep
if isinstance(sep, str):
sep = [sep]
if nheaders > 0:
h = [""] * nheaders
else:
h = ""
u, f = "", ""
for j in range(length):
if j >= len(just):
cj = just[-1]
else:
cj = just[j]
if j >= len(sep):
csep = sep[-1]
else:
csep = sep[j]
if isinstance(csep, int):
csep = " " * csep
w = max(widths[j], mxlengths[j])
if nheaders > 0:
for k in range(nheaders):
h[k] += csep + strexp(headers[k][j], w, just=cj)
else:
h += csep + strexp(headers[j], w, just=cj)
u += csep + ulchar * w
f += csep + formats[j]
if nheaders > 0:
h = [hj.rstrip() + "\n" for hj in h]
else:
h = h.rstrip() + "\n"
u = u.rstrip() + "\n"
f = f.rstrip() + "\n"
return "".join(h) + u, f