Skip to content

Commit

Permalink
Merge pull request #27 from tenuki/easier-formatting
Browse files Browse the repository at this point in the history
Easier formatting, allow callable as a column datatype
  • Loading branch information
foutaise authored Oct 22, 2017
2 parents 4db57a3 + d974259 commit 0d1c17c
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 41 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ DESCRIPTION
| Bourgeau | | Loue |
+----------+-----+----------+
text float exp int auto
===========================================
abcd 67.000 6.540e+02 89 128.001
efgh 67.543 6.540e-01 90 1.280e+22
ijkl 0.000 5.000e-78 89 0.000
mnop 0.023 5.000e+78 92 1.280e+22
text float exp int auto
==============================================
abcd 67.000 6.540e+02 89 128.001
efghijk 67.543 6.540e-01 90 1.280e+22
lmn 0.000 5.000e-78 89 0.000
opqrstu 0.023 5.000e+78 92 1.280e+22
CLASSES
class Texttable
Expand Down Expand Up @@ -129,13 +129,15 @@ CLASSES
| set_cols_dtype(self, array)
| Set the desired columns datatype for the cols.
|
| - the elements of the array should be either "a", "t", "f", "e" or "i":
| - the elements of the array should be either a callable or any of:
| "a", "t", "f", "e" or "i":
|
| * "a": automatic (try to use the most appropriate datatype)
| * "t": treat as text
| * "f": treat as float in decimal format
| * "e": treat as float in exponential format
| * "i": treat as int
| * a callable: should return formatted string for any value given
|
| - by default, automatic datatyping is used for each column
|
Expand Down
42 changes: 32 additions & 10 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from textwrap import dedent
from texttable import Texttable

if sys.version >= '3':
u_dedent = dedent
else:
def u_dedent(b):
return unicode(dedent(b), 'utf-8')

def clean(text):
return re.sub(r'( +)$', '', text, flags=re.MULTILINE) + '\n'

Expand Down Expand Up @@ -133,11 +139,6 @@ def test_obj2unicode():
''')

def test_combining_char():
if sys.version >= '3':
u_dedent = dedent
else:
def u_dedent(b):
return unicode(dedent(b), 'utf-8')
table = Texttable()
table.set_cols_align(["l", "r", "r"])
table.add_rows([
Expand All @@ -157,11 +158,6 @@ def u_dedent(b):
''')

def test_combining_char2():
if sys.version >= '3':
u_dedent = dedent
else:
def u_dedent(b):
return unicode(dedent(b), 'utf-8')
table = Texttable()
table.add_rows([
["a", "b", "c"],
Expand All @@ -174,3 +170,29 @@ def u_dedent(b):
| 诶诶诶 | bbb | 西西西 |
+--------+-----+--------+
''')


def test_user_dtype():
table = Texttable()

table.set_cols_align(["l", "r", "r"])
table.set_cols_dtype([
'a', # automatic
lambda s:str(s)+"s", # user-def
lambda s:('%s'%s) if s>=0 else '[%s]'%(-s), # user-def
])
table.add_rows([
["str", "code-point\nlength", "display\nwidth"],
["a", 2, 1],
["a", 1,-3],
])
assert clean(table.draw()) == u_dedent('''\
+-----+------------+---------+
| str | code-point | display |
| | length | width |
+=====+============+=========+
| a | 2s | 1 |
+-----+------------+---------+
| a | 1s | [3] |
+-----+------------+---------+
''')
108 changes: 84 additions & 24 deletions texttable.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ def __str__(self):
return self.msg


class FallbackToText(Exception):
"""Used for failed conversion to float"""
pass


class Texttable:

BORDER = 1
Expand Down Expand Up @@ -272,13 +277,15 @@ def set_cols_valign(self, array):
def set_cols_dtype(self, array):
"""Set the desired columns datatype for the cols.
- the elements of the array should be either "a", "t", "f", "e" or "i":
- the elements of the array should be either a callable or any of "a",
"t", "f", "e" or "i":
* "a": automatic (try to use the most appropriate datatype)
* "t": treat as text
* "f": treat as float in decimal format
* "e": treat as float in exponential format
* "i": treat as int
* a callable: should return formatted string for any value given
- by default, automatic datatyping is used for each column
"""
Expand Down Expand Up @@ -387,39 +394,92 @@ def draw(self):
out += self._hline()
return out[:-1]

@classmethod
def _to_float(cls, x):
if x is None:
raise FallbackToText()
try:
return float(x)
except ValueError:
raise FallbackToText()

@classmethod
def _fmt_int(cls, x, **kw):
"""Integer formatting class-method.
- x will be float-converted and then used.
"""
return str(int(round(cls._to_float(x))))

@classmethod
def _fmt_float(cls, x, **kw):
"""Float formatting class-method.
- x parameter is ignored. Instead kw-argument f being x float-converted
will be used.
- precision will be taken from `n` kw-argument.
"""
n = kw.get('n')
return '%.*f' % (n, cls._to_float(x))

@classmethod
def _fmt_exp(cls, x, **kw):
"""Exponential formatting class-method.
- x parameter is ignored. Instead kw-argument f being x float-converted
will be used.
- precision will be taken from `n` kw-argument.
"""
n = kw.get('n')
return '%.*e' % (n, cls._to_float(x))

@classmethod
def _fmt_text(cls, x, **kw):
"""String formatting class-method."""
return obj2unicode(x)

@classmethod
def _fmt_auto(cls, x, **kw):
"""auto formatting class-method."""
f = cls._to_float(x)
if abs(f) > 1e8:
fn = cls._fmt_exp
else:
if f - round(f) == 0:
fn = cls._fmt_int
else:
fn = cls._fmt_float
return fn(x, **kw)

def _str(self, i, x):
"""Handles string formatting of cell data
i - index of the cell datatype in self._dtype
x - cell data to format
"""
try:
f = float(x)
except:
return obj2unicode(x)
FMT = {
'a':self._fmt_auto,
'i':self._fmt_int,
'f':self._fmt_float,
'e':self._fmt_exp,
't':self._fmt_text,
}

n = self._precision
dtype = self._dtype[i]
if not callable(dtype):
dtype = FMT[dtype]

try:
try:
return dtype(x, n=n)
except TypeError:
return dtype(x)
except FallbackToText:
return self._fmt_text(x)

if dtype == 'i':
return str(int(round(f)))
elif dtype == 'f':
return '%.*f' % (n, f)
elif dtype == 'e':
return '%.*e' % (n, f)
elif dtype == 't':
return obj2unicode(x)
else:
if f - round(f) == 0:
if abs(f) > 1e8:
return '%.*e' % (n, f)
else:
return str(int(round(f)))
else:
if abs(f) > 1e8:
return '%.*e' % (n, f)
else:
return '%.*f' % (n, f)

def _check_row_size(self, array):
"""Check that the specified array fits the previous rows size
Expand Down

0 comments on commit 0d1c17c

Please sign in to comment.