1010"""
1111import fnmatch
1212import inspect
13+ import itertools
1314import os
1415import re
1516import sys
@@ -32,48 +33,56 @@ def get_lines_from_file(filename, lineno, context_lines, loader=None, module_nam
3233 Returns context_lines before and after lineno from file.
3334 Returns (pre_context_lineno, pre_context, context_line, post_context).
3435 """
36+ lineno = lineno - 1
37+ lower_bound = max (0 , lineno - context_lines )
38+ upper_bound = lineno + context_lines
39+
3540 source = None
3641 if loader is not None and hasattr (loader , "get_source" ):
37- try :
38- source = loader .get_source (module_name )
39- except ImportError :
40- # ImportError: Loader for module cProfile cannot handle module __main__
41- source = None
42- if source is not None :
43- source = source .splitlines ()
42+ result = get_source_lines_from_loader (loader , module_name , lineno , lower_bound , upper_bound )
43+ if result is not None :
44+ return result
45+
4446 if source is None :
4547 try :
46- f = open (filename , 'rb' )
47- try :
48- source = f .readlines ()
49- finally :
50- f .close ()
51- except (OSError , IOError ):
48+ with open (filename , 'rb' ) as file_obj :
49+ encoding = 'utf8'
50+ # try to find encoding of source file by "coding" header
51+ # if none is found, utf8 is used as a fallback
52+ for line in itertools .islice (file_obj , 0 , 2 ):
53+ match = _coding_re .search (line .decode ('utf8' ))
54+ if match :
55+ encoding = match .group (1 )
56+ break
57+ file_obj .seek (0 )
58+ lines = [compat .text_type (line , encoding , 'replace' )
59+ for line in itertools .islice (file_obj , lower_bound , upper_bound + 1 )]
60+ offset = lineno - lower_bound
61+ return (
62+ [l .strip ('\r \n ' ) for l in lines [0 :offset ]],
63+ lines [offset ].strip ('\r \n ' ),
64+ [l .strip ('\r \n ' ) for l in lines [offset + 1 :]] if len (lines ) > offset else []
65+ )
66+ except (OSError , IOError , IndexError ):
5267 pass
68+ return None , None , None
5369
54- if source is None :
55- return None , None , None
56- encoding = 'utf8'
57- for line in source [:2 ]:
58- # File coding may be specified. Match pattern from PEP-263
59- # (http://www.python.org/dev/peps/pep-0263/)
60- match = _coding_re .search (line .decode ('utf8' )) # let's assume utf8
61- if match :
62- encoding = match .group (1 )
63- break
64- source = [compat .text_type (sline , encoding , 'replace' ) for sline in source ]
65-
66- lower_bound = max (0 , lineno - context_lines )
67- upper_bound = lineno + 1 + context_lines
6870
71+ def get_source_lines_from_loader (loader , module_name , lineno , lower_bound , upper_bound ):
72+ try :
73+ source = loader .get_source (module_name )
74+ except ImportError :
75+ # ImportError: Loader for module cProfile cannot handle module __main__
76+ return None
77+ if source is not None :
78+ source = source .splitlines ()
6979 try :
7080 pre_context = [line .strip ('\r \n ' ) for line in source [lower_bound :lineno ]]
7181 context_line = source [lineno ].strip ('\r \n ' )
72- post_context = [line .strip ('\r \n ' ) for line in source [(lineno + 1 ):upper_bound ]]
82+ post_context = [line .strip ('\r \n ' ) for line in source [(lineno + 1 ):upper_bound + 1 ]]
7383 except IndexError :
7484 # the file may have changed since it was loaded into memory
7585 return None , None , None
76-
7786 return pre_context , context_line , post_context
7887
7988
@@ -181,9 +190,6 @@ def get_frame_info(frame, lineno, with_locals=True,
181190 abs_path = None
182191 function = None
183192
184- if lineno :
185- lineno -= 1
186-
187193 # Try to pull a relative file path
188194 # This changes /foo/site-packages/baz/bar.py into baz/bar.py
189195 try :
@@ -200,7 +206,7 @@ def get_frame_info(frame, lineno, with_locals=True,
200206 'filename' : filename ,
201207 'module' : module_name ,
202208 'function' : function ,
203- 'lineno' : lineno + 1 ,
209+ 'lineno' : lineno ,
204210 'library_frame' : is_library_frame (abs_path , include_paths_re , exclude_paths_re )
205211 }
206212
@@ -212,11 +218,9 @@ def get_frame_info(frame, lineno, with_locals=True,
212218 else :
213219 pre_context , context_line , post_context = [], None , []
214220 if context_line :
215- frame_result .update ({
216- 'pre_context' : pre_context ,
217- 'context_line' : context_line ,
218- 'post_context' : post_context ,
219- })
221+ frame_result ['pre_context' ] = pre_context
222+ frame_result ['context_line' ] = context_line
223+ frame_result ['post_context' ] = post_context
220224 if with_locals :
221225 if f_locals is not None and not isinstance (f_locals , dict ):
222226 # XXX: Genshi (and maybe others) have broken implementations of
0 commit comments