Skip to content

Commit bc5ddca

Browse files
committed
Fix issues with call printouts not showing the full function definition. Fixes #8.
1 parent ffc1bcd commit bc5ddca

1 file changed

Lines changed: 59 additions & 6 deletions

File tree

src/hunter.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,19 @@
77
import linecache
88
import os
99
import pdb
10+
import re
1011
import sys
12+
import tokenize
13+
import types
14+
15+
from functools import partial
1116
from itertools import chain
1217

18+
from colorama import AnsiToWin32
19+
from colorama import Fore
20+
from colorama import Style
1321
from 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+
197239
def 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

Comments
 (0)