Skip to content

Commit b806ec2

Browse files
committed
pretty printing output
1 parent b2400aa commit b806ec2

File tree

8 files changed

+136
-75
lines changed

8 files changed

+136
-75
lines changed

devtools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# flake8: noqa
2+
from .ansi import *
23
from .debug import *
4+
from .prettier import *
35
from .version import VERSION

devtools/ansi.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1+
import re
12
import sys
23
from enum import IntEnum
4+
from typing import Any
35

46
_ansi_template = '\033[{}m'
7+
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
58

9+
__all__ = ['sformat', 'sprint']
610

7-
def isatty(stream):
11+
12+
def isatty(stream=None):
13+
stream = stream or sys.stdout
814
try:
915
return stream.isatty()
1016
except Exception:
1117
return False
1218

1319

20+
def strip_ansi(value):
21+
return _ansi_re.sub('', value)
22+
23+
1424
class Style(IntEnum):
1525
"""
1626
Heavily borrowed from https://github.com/pallets/click/blob/6.7/click/termui.py
@@ -62,7 +72,7 @@ class Style(IntEnum):
6272
# this is a meta value used for the "Style" instance which is the "style" function
6373
function = -1
6474

65-
def __call__(self, text: str, *styles, reset: bool=True):
75+
def __call__(self, text: Any, *styles, reset: bool=True):
6676
"""
6777
Styles a text with ANSI styles and returns the new string.
6878
@@ -103,9 +113,9 @@ def __call__(self, text: str, *styles, reset: bool=True):
103113
codes.append(str(s.value))
104114

105115
if codes:
106-
r = _ansi_template.format(';'.join(codes)) + text
116+
r = _ansi_template.format(';'.join(codes)) + str(text)
107117
else:
108-
r = text
118+
r = str(text)
109119

110120
if reset:
111121
r += _ansi_template.format(self.reset)
@@ -128,10 +138,10 @@ def __str__(self):
128138
return super().__str__()
129139

130140

131-
style = Style(-1)
141+
sformat = Style(-1)
132142

133143

134144
def sprint(text, *styles, reset=True, flush=True, file=None, **print_kwargs):
135-
if isatty(file or sys.stdout):
136-
text = style(text, *styles, reset=reset)
145+
if isatty(file):
146+
text = sformat(text, *styles, reset=reset)
137147
print(text, flush=flush, file=file, **print_kwargs)

devtools/debug.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
from textwrap import dedent
88
from typing import Generator, List, Optional, Tuple
99

10+
from .ansi import isatty, sformat
11+
from .prettier import PrettyFormat
12+
1013
__all__ = ['Debug', 'debug']
1114
CWD = Path('.').resolve()
15+
pformat = PrettyFormat()
1216

1317

1418
def env_true(var_name, alt='FALSE'):
@@ -26,18 +30,13 @@ def __init__(self, value, *, name=None, **extra):
2630
self.extra.append(('len', len(value)))
2731
self.extra += [(k, v) for k, v in extra.items() if v is not None]
2832

29-
def value_str(self):
30-
if isinstance(self.value, str):
31-
return '"{}"'.format(self.value)
32-
return str(self.value)
33-
3433
def __str__(self) -> str:
3534
template = '{value} ({self.value.__class__.__name__}) {extra}'
3635
if self.name:
3736
template = '{self.name} = ' + template
3837
return template.format(
3938
self=self,
40-
value=self.value_str(),
39+
value=pformat(self.value, indent=2),
4140
extra=' '.join('{}={}'.format(k, v) for k, v in self.extra)
4241
).rstrip(' ') # trailing space if extra is empty
4342

@@ -55,9 +54,19 @@ def __init__(self, *, filename: str, lineno: int, frame: str, arguments: List[De
5554
self.frame = frame
5655
self.arguments = arguments
5756

57+
def str(self, colours=False) -> str:
58+
if colours:
59+
prefix = '{}:{} {}\n '.format(
60+
sformat(self.filename, sformat.magenta),
61+
sformat(self.lineno, sformat.green),
62+
sformat(self.frame, sformat.green, sformat.italic)
63+
)
64+
else:
65+
prefix = '{0.filename}:{0.lineno} {0.frame}\n '.format(self)
66+
return prefix + '\n '.join(str(a) for a in self.arguments)
67+
5868
def __str__(self) -> str:
59-
template = '{s.filename}:{s.lineno} {s.frame}\n {a}'
60-
return template.format(s=self, a='\n '.join(str(a) for a in self.arguments))
69+
return self.str()
6170

6271
def __repr__(self) -> str:
6372
arguments = ' '.join(str(a) for a in self.arguments)
@@ -88,13 +97,15 @@ def _env_bool(cls, value, env_name, env_default='TRUE'):
8897
else:
8998
return value
9099

91-
def __call__(self, *args, **kwargs):
92-
print(self._process(args, kwargs, r'debug *\('), flush=True)
100+
def __call__(self, *args, file_=None, flush_=True, **kwargs) -> None:
101+
d_out = self._process(args, kwargs, r'debug *\(')
102+
s = d_out.str(isatty(file_))
103+
print(s, file=file_, flush=flush_)
93104

94-
def format(self, *args, **kwargs):
105+
def format(self, *args, **kwargs) -> DebugOutput:
95106
return self._process(args, kwargs, r'debug.format *\(')
96107

97-
def _process(self, args, kwargs, func_regex):
108+
def _process(self, args, kwargs, func_regex) -> DebugOutput:
98109
curframe = inspect.currentframe()
99110
frames = inspect.getouterframes(curframe, context=self._frame_context_length)
100111
# BEWARE: this must be call by a method which in turn is called "directly" for the frame to be correct

devtools/prettier.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import textwrap
44
from typing import Any, Generator, Union
55

6+
from .ansi import isatty
7+
68
try:
79
from pygments import highlight
810
from pygments.lexers import PythonLexer
@@ -16,6 +18,7 @@
1618
(list, '[', ']'),
1719
(set, '{', '}'),
1820
]
21+
__all__ = ['PrettyFormat', 'pformat', 'pprint']
1922

2023

2124
class PrettyFormat:
@@ -42,9 +45,9 @@ def __init__(self,
4245
(collections.Generator, self._format_generators),
4346
]
4447

45-
def __call__(self, value: Any, *, indent_current: int=0):
48+
def __call__(self, value: Any, *, indent: int=0, indent_first: bool=False):
4649
self._stream = io.StringIO()
47-
self._format(value, indent_current=indent_current, indent_first=True)
50+
self._format(value, indent_current=indent, indent_first=indent_first)
4851
s = self._stream.getvalue()
4952
if self._colorize and pyg_lexer:
5053
s = highlight(s, lexer=pyg_lexer, formatter=pyg_formatter)
@@ -142,7 +145,7 @@ def _format_raw(self, value: Any, value_repr: str, indent_current: int, indent_n
142145

143146

144147
pformat = PrettyFormat()
145-
_ppformat = PrettyFormat(colorize=True)
148+
_ppformat = PrettyFormat(colorize=isatty())
146149

147150

148151
def pprint(s):

tests/test_ansi.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,48 @@
22

33
import pytest
44

5-
from devtools.ansi import sprint, style
5+
from devtools.ansi import sformat, sprint
66

77

88
def test_colorize():
9-
v = style('hello', style.red)
9+
v = sformat('hello', sformat.red)
1010
assert v == '\x1b[31mhello\x1b[0m'
1111

1212

1313
def test_no_reset():
14-
v = style('hello', style.bold, reset=False)
14+
v = sformat('hello', sformat.bold, reset=False)
1515
assert v == '\x1b[1mhello'
1616

1717

1818
def test_combine_styles():
19-
v = style('hello', style.red, style.bold)
19+
v = sformat('hello', sformat.red, sformat.bold)
2020
assert v == '\x1b[31;1mhello\x1b[0m'
2121

2222

2323
def test_no_styles():
24-
v = style('hello')
24+
v = sformat('hello')
2525
assert v == 'hello\x1b[0m'
2626

2727

2828
def test_style_str():
29-
v = style('hello', 'red')
29+
v = sformat('hello', 'red')
3030
assert v == '\x1b[31mhello\x1b[0m'
3131

3232

33+
def test_non_str_input():
34+
v = sformat(12.2, sformat.yellow, sformat.italic)
35+
assert v == '\x1b[33;3m12.2\x1b[0m'
36+
37+
3338
def test_invalid_style_str():
3439
with pytest.raises(ValueError) as exc_info:
35-
style('x', 'mauve')
40+
sformat('x', 'mauve')
3641
assert exc_info.value.args[0] == 'invalid style "mauve"'
3742

3843

3944
def test_print_not_tty():
4045
stream = io.StringIO()
41-
sprint('hello', style.green, file=stream)
46+
sprint('hello', sformat.green, file=stream)
4247
out = stream.getvalue()
4348
assert out == 'hello\n'
4449

@@ -49,7 +54,7 @@ def isatty(self):
4954
return True
5055

5156
stream = TTYStream()
52-
sprint('hello', style.green, file=stream)
57+
sprint('hello', sformat.green, file=stream)
5358
out = stream.getvalue()
5459
assert out == '\x1b[32mhello\x1b[0m\n', repr(out)
5560

@@ -60,21 +65,21 @@ def isatty(self):
6065
raise RuntimeError('boom')
6166

6267
stream = TTYStream()
63-
sprint('hello', style.green, file=stream)
68+
sprint('hello', sformat.green, file=stream)
6469
out = stream.getvalue()
6570
assert out == 'hello\n'
6671

6772

6873
def test_get_styles():
69-
assert style.styles['bold'] == 1
70-
assert style.styles['un_bold'] == 22
74+
assert sformat.styles['bold'] == 1
75+
assert sformat.styles['un_bold'] == 22
7176

7277

7378
def test_repr():
74-
assert repr(style) == '<pseudo function style(text, *styles)>'
75-
assert repr(style.red) == '<Style.red: 31>'
79+
assert repr(sformat) == '<pseudo function style(text, *styles)>'
80+
assert repr(sformat.red) == '<Style.red: 31>'
7681

7782

7883
def test_str():
79-
assert str(style) == '<pseudo function style(text, *styles)>'
80-
assert str(style.red) == 'Style.red'
84+
assert str(sformat) == '<pseudo function style(text, *styles)>'
85+
assert str(sformat.red) == 'Style.red'

tests/test_expr_render.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,25 @@ def test_exotic_types():
4040
print(s)
4141
# list and generator comprehensions are wrong because ast is wrong, see https://bugs.python.org/issue31241
4242
assert (
43-
'tests/test_expr_render.py:<line no> test_exotic_types\n'
44-
' sum(aa) = 6 (int)\n'
45-
' 1 == 2 = False (bool)\n'
46-
' 1 < 2 = True (bool)\n'
47-
' 1 << 2 = 4 (int)\n'
48-
' \'t\' if True else \'f\' = "t" (str) len=1\n'
49-
' 1 or 2 = 1 (int)\n'
50-
' [a for a in aa] = [1, 2, 3] (list)\n'
51-
' {a for a in aa} = {1, 2, 3} (set)\n'
52-
' {a: a + 1 for a in aa} = {1: 2, 2: 3, 3: 4} (dict)\n'
53-
' (a for a in aa) = <generator object test_exotic_types.<locals>.<genexpr> at 0x<hash>> (generator)'
43+
"tests/test_expr_render.py:<line no> test_exotic_types\n"
44+
" sum(aa) = 6 (int)\n"
45+
" 1 == 2 = False (bool)\n"
46+
" 1 < 2 = True (bool)\n"
47+
" 1 << 2 = 4 (int)\n"
48+
" 't' if True else 'f' = 't' (str) len=1\n"
49+
" 1 or 2 = 1 (int)\n"
50+
" [a for a in aa] = [1, 2, 3] (list)\n"
51+
" {a for a in aa} = {1, 2, 3} (set)\n"
52+
" {a: a + 1 for a in aa} = {\n"
53+
" 1: 2,\n"
54+
" 2: 3,\n"
55+
" 3: 4,\n"
56+
" } (dict)\n"
57+
" (a for a in aa) = (\n"
58+
" 1,\n"
59+
" 2,\n"
60+
" 3,\n"
61+
" ) (generator)"
5462
) == s
5563

5664

0 commit comments

Comments
 (0)