77from textwrap import dedent
88from typing import Generator , List , Optional , Tuple
99
10+ from .ansi import isatty , sformat
11+ from .prettier import PrettyFormat
12+
1013__all__ = ['Debug' , 'debug' ]
1114CWD = Path ('.' ).resolve ()
15+ pformat = PrettyFormat ()
1216
1317
1418def env_true (var_name , alt = 'FALSE' ):
@@ -26,20 +30,21 @@ 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+ 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
3345
3446 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 ()
4348
4449
4550class DebugOutput :
@@ -55,13 +60,19 @@ def __init__(self, *, filename: str, lineno: int, frame: str, arguments: List[De
5560 self .frame = frame
5661 self .arguments = arguments
5762
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+
5874 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 ()
6576
6677 def __repr__ (self ) -> str :
6778 arguments = ' ' .join (str (a ) for a in self .arguments )
@@ -78,21 +89,30 @@ class Debug:
7889
7990 def __init__ (self , * ,
8091 warnings : Optional [bool ]= None ,
92+ colours : Optional [bool ]= None ,
8193 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' )
8696 # 50 lines should be enough to make sure we always get the entire function definition
8797 self ._frame_context_length = frame_context_length
8898
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_ )
91111
92- def format (self , * args , ** kwargs ):
112+ def format (self , * args , ** kwargs ) -> DebugOutput :
93113 return self ._process (args , kwargs , r'debug.format *\(' )
94114
95- def _process (self , args , kwargs , func_regex ):
115+ def _process (self , args , kwargs , func_regex ) -> DebugOutput :
96116 curframe = inspect .currentframe ()
97117 frames = inspect .getouterframes (curframe , context = self ._frame_context_length )
98118 # BEWARE: this must be call by a method which in turn is called "directly" for the frame to be correct
0 commit comments