77import linecache
88import os
99import pdb
10+ import re
1011import sys
12+ import tokenize
13+ import types
14+
15+ from functools import partial
1116from itertools import chain
1217
18+ from colorama import AnsiToWin32
19+ from colorama import Fore
20+ from colorama import Style
1321from fields import Fields
14- from colorama import AnsiToWin32 , Fore , Style
22+
1523
1624
1725__version__ = "0.2.1"
@@ -160,7 +168,7 @@ def globals(self):
160168
161169 @CachedProperty
162170 def function (self ):
163- return self .frame . f_code .co_name
171+ return self .code .co_name
164172
165173 @CachedProperty
166174 def module (self ):
@@ -182,18 +190,52 @@ def lineno(self):
182190 return self .frame .f_lineno
183191
184192 @CachedProperty
185- def line (self , getline = linecache .getline ):
193+ def code (self ):
194+ return self .frame .f_code
195+
196+ @CachedProperty
197+ def source (self , getlines = linecache .getlines ):
186198 """
187199 Get a line from ``linecache``. Ignores failures somewhat.
188200 """
189201 try :
190- return getline (self .filename , self .lineno )
202+ if self .kind == 'call' and not isinstance (self .code , types .ModuleType ):
203+ lines = []
204+ try :
205+ for _ , token , _ , _ , line in tokenize .generate_tokens (partial (
206+ next ,
207+ yield_lines (self .filename , self .lineno - 1 , lines .append )
208+ )):
209+ if token in ("def" , "class" , "lambda" ):
210+ return '' .join (lines )
211+ except tokenize .TokenError :
212+ pass
213+
214+ return getlines (self .filename )[self .lineno - 1 ]
191215 except Exception as exc :
192- return "??? no source: {} ???" .format (exc )
216+ return "??? no source: {!r } ???" .format (exc )
193217
194218 __getitem__ = object .__getattribute__
195219
196220
221+ def yield_lines (filename , start , collector ,
222+ limit = 10 ,
223+ getlines = linecache .getlines ,
224+ leading_whitespace_re = re .compile ('(^[ \t ]*)(?:[^ \t \n ])' , re .MULTILINE )):
225+ dedent = None
226+ amount = 0
227+ for line in getlines (filename )[start :start + limit ]:
228+ if dedent is None :
229+ dedent = leading_whitespace_re .findall (line )
230+ dedent = dedent [0 ] if dedent else ""
231+ amount = len (dedent )
232+ elif not line .startswith (dedent ):
233+ break
234+ # print(start, repr(line))
235+ collector (line )
236+ yield line [amount :]
237+
238+
197239def Q (* predicates , ** query ):
198240 """
199241 Handles situations where :class:`hunter.Query` objects (or other callables) are passed in as positional arguments.
@@ -419,15 +461,26 @@ def __call__(self, event, sep=os.path.sep, join=os.path.join):
419461 filename = event .filename or "<???>"
420462 # TODO: support auto-alignment, need a context object for this, eg:
421463 # alignment = context.filename_alignment = max(getattr(context, 'filename_alignment', self.filename_alignment), len(filename))
464+ lines = event .source .rstrip ().splitlines ()
422465 self .stream .write ("{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} {code}{}{reset}\n " .format (
423466 join (* filename .split (sep )[- 2 :]),
424467 event .lineno ,
425468 event .kind ,
426- event . line . rstrip () ,
469+ lines [ 0 ] ,
427470 align = self .filename_alignment ,
428471 code = self .code_colors [event .kind ],
429472 ** self .event_colors
430473 ))
474+ for line in lines [1 :]:
475+ self .stream .write ("{:>{align}} {kind}{:9} {code}{}{reset}\n " .format (
476+ "" ,
477+ r" |" ,
478+ line ,
479+ align = self .filename_alignment ,
480+ code = self .code_colors [event .kind ],
481+ ** self .event_colors
482+ ))
483+
431484 if event .kind in ('return' , 'exception' ):
432485 self .stream .write ("{:>{align}} {continuation}{:9} {color}{} value: {detail}{!r}{reset}\n " .format (
433486 "" ,
0 commit comments