7
7
from textwrap import dedent
8
8
from typing import Generator , List , Optional , Tuple
9
9
10
+ from .ansi import isatty , sformat
11
+ from .prettier import PrettyFormat
12
+
10
13
__all__ = ['Debug' , 'debug' ]
11
14
CWD = Path ('.' ).resolve ()
15
+ pformat = PrettyFormat ()
12
16
13
17
14
18
def env_true (var_name , alt = 'FALSE' ):
@@ -26,20 +30,21 @@ def __init__(self, value, *, name=None, **extra):
26
30
self .extra .append (('len' , len (value )))
27
31
self .extra += [(k , v ) for k , v in extra .items () if v is not None ]
28
32
29
- def value_str (self ):
30
- if isinstance (self .value , str ):
31
- return '"{}"' .format (self .value )
32
- return str (self .value )
33
+ def str (self , colours = False ) -> str :
34
+ s = ''
35
+ if self .name :
36
+ s = sformat (self .name , sformat .blue , apply = colours ) + ': '
37
+ s += pformat (self .value , indent = 2 )
38
+ suffix = (
39
+ ' ({0.value.__class__.__name__}) {1}'
40
+ .format (self , ' ' .join ('{}={}' .format (k , v ) for k , v in self .extra ))
41
+ .rstrip (' ' ) # trailing space if extra is empty
42
+ )
43
+ s += sformat (suffix , sformat .dim , apply = colours )
44
+ return s
33
45
34
46
def __str__ (self ) -> str :
35
- template = '{value} ({self.value.__class__.__name__}) {extra}'
36
- if self .name :
37
- template = '{self.name} = ' + template
38
- return template .format (
39
- self = self ,
40
- value = self .value_str (),
41
- extra = ' ' .join ('{}={}' .format (k , v ) for k , v in self .extra )
42
- ).rstrip (' ' ) # trailing space if extra is empty
47
+ return self .str ()
43
48
44
49
45
50
class DebugOutput :
@@ -55,13 +60,19 @@ def __init__(self, *, filename: str, lineno: int, frame: str, arguments: List[De
55
60
self .frame = frame
56
61
self .arguments = arguments
57
62
63
+ def str (self , colours = False ) -> str :
64
+ if colours :
65
+ prefix = '{}:{} {}\n ' .format (
66
+ sformat (self .filename , sformat .magenta ),
67
+ sformat (self .lineno , sformat .green ),
68
+ sformat (self .frame , sformat .green , sformat .italic )
69
+ )
70
+ else :
71
+ prefix = '{0.filename}:{0.lineno} {0.frame}\n ' .format (self )
72
+ return prefix + '\n ' .join (a .str (colours ) for a in self .arguments )
73
+
58
74
def __str__ (self ) -> str :
59
- template = '{s.filename}:{s.lineno} {s.frame}'
60
- # turns out single line output is ugly
61
- # if len(self.arguments) == 1:
62
- # return (template + ': {a}').format(s=self, a=self.arguments[0])
63
- # else:
64
- return (template + '\n {a}' ).format (s = self , a = '\n ' .join (str (a ) for a in self .arguments ))
75
+ return self .str ()
65
76
66
77
def __repr__ (self ) -> str :
67
78
arguments = ' ' .join (str (a ) for a in self .arguments )
@@ -78,21 +89,30 @@ class Debug:
78
89
79
90
def __init__ (self , * ,
80
91
warnings : Optional [bool ]= None ,
92
+ colours : Optional [bool ]= None ,
81
93
frame_context_length : int = 50 ):
82
- if warnings is None :
83
- self ._warnings = env_true ('PY_DEVTOOLS_WARNINGS' , 'TRUE' )
84
- else :
85
- self ._warnings = warnings
94
+ self ._warnings = self ._env_bool (warnings , 'PY_DEVTOOLS_WARNINGS' )
95
+ self ._colours = self ._env_bool (colours , 'PY_DEVTOOLS_COLOURS' )
86
96
# 50 lines should be enough to make sure we always get the entire function definition
87
97
self ._frame_context_length = frame_context_length
88
98
89
- def __call__ (self , * args , ** kwargs ):
90
- print (self ._process (args , kwargs , r'debug *\(' ), flush = True )
99
+ @classmethod
100
+ def _env_bool (cls , value , env_name , env_default = 'TRUE' ):
101
+ if value is None :
102
+ return env_true (env_name , env_default )
103
+ else :
104
+ return value
105
+
106
+ def __call__ (self , * args , file_ = None , flush_ = True , ** kwargs ) -> None :
107
+ d_out = self ._process (args , kwargs , r'debug *\(' )
108
+ colours_possible = isatty (file_ )
109
+ s = d_out .str (self ._colours and colours_possible )
110
+ print (s , file = file_ , flush = flush_ )
91
111
92
- def format (self , * args , ** kwargs ):
112
+ def format (self , * args , ** kwargs ) -> DebugOutput :
93
113
return self ._process (args , kwargs , r'debug.format *\(' )
94
114
95
- def _process (self , args , kwargs , func_regex ):
115
+ def _process (self , args , kwargs , func_regex ) -> DebugOutput :
96
116
curframe = inspect .currentframe ()
97
117
frames = inspect .getouterframes (curframe , context = self ._frame_context_length )
98
118
# BEWARE: this must be call by a method which in turn is called "directly" for the frame to be correct
0 commit comments