1313#
1414# See the Apache Version 2.0 License for specific language governing
1515# permissions and limitations under the License.
16+ # With number of modifications by Don Jayamanne
1617
1718from __future__ import with_statement
1819
@@ -174,6 +175,26 @@ def __len__(self):
174175BREAKPOINT_PASS_COUNT_WHEN_EQUAL = 2
175176BREAKPOINT_PASS_COUNT_WHEN_EQUAL_OR_GREATER = 3
176177
178+ ## Begin modification by Don Jayamanne
179+ DJANGO_VERSIONS_IDENTIFIED = False
180+ IS_DJANGO18 = False
181+ IS_DJANGO19 = False
182+ IS_DJANGO19_OR_HIGHER = False
183+
184+ try :
185+ dict_contains = dict .has_key
186+ except :
187+ try :
188+ #Py3k does not have has_key anymore, and older versions don't have __contains__
189+ dict_contains = dict .__contains__
190+ except :
191+ try :
192+ dict_contains = dict .has_key
193+ except NameError :
194+ def dict_contains (d , key ):
195+ return d .has_key (key )
196+ ## End modification by Don Jayamanne
197+
177198class BreakpointInfo (object ):
178199 __slots__ = [
179200 'breakpoint_id' , 'filename' , 'lineno' , 'condition_kind' , 'condition' ,
@@ -619,19 +640,57 @@ def update_all_thread_stacks(blocking_thread = None, check_is_blocked = True):
619640 cur_thread ._block_starting_lock .release ()
620641
621642DJANGO_BREAKPOINTS = {}
643+ DJANGO_TEMPLATES = {}
622644
623645class DjangoBreakpointInfo (object ):
624646 def __init__ (self , filename ):
625647 self ._line_locations = None
626648 self .filename = filename
627649 self .breakpoints = {}
650+ self .rangeIsPlainText = {}
628651
629652 def add_breakpoint (self , lineno , brkpt_id ):
630653 self .breakpoints [lineno ] = brkpt_id
631654
632655 def remove_breakpoint (self , lineno ):
633- del self .breakpoints [lineno ]
634-
656+ try :
657+ del self .breakpoints [lineno ]
658+ except :
659+ pass
660+
661+ def has_breakpoint_for_line (self , lineNumber ):
662+ return self .breakpoints .get (lineNumber ) is not None
663+
664+ def is_range_plain_text (self , start , end ):
665+ key = str (start ) + '-' + str (end )
666+ if self .rangeIsPlainText .get (key ) is None :
667+ # we need to calculate our line number offset information
668+ try :
669+ with open (self .filename , 'rb' ) as contents :
670+ contents .seek (start , 0 ) # 0 = start of file, optional in this case
671+ data = contents .read (end - start )
672+ isPlainText = True
673+ if data .startswith ('{{' ) and data .endswith ('}}' ):
674+ isPlainText = False
675+ if data .startswith ('{%' ) and data .endswith ('%}' ):
676+ isPlainText = False
677+ self .rangeIsPlainText [key ] = isPlainText
678+ return isPlainText
679+ except :
680+ return False
681+ else :
682+ return self .rangeIsPlainText .get (key )
683+
684+ def line_number_to_offset (self , lineNumber ):
685+ line_locs = self .line_locations
686+ if line_locs is not None :
687+ low_line = line_locs [lineNumber - 1 ]
688+ hi_line = line_locs [lineNumber ]
689+
690+ return low_line , hi_line
691+
692+ return (None , None )
693+
635694 @property
636695 def line_locations (self ):
637696 if self ._line_locations is None :
@@ -680,28 +739,42 @@ def should_break(self, start, end):
680739 return False , 0
681740
682741def get_django_frame_source (frame ):
742+ global DJANGO_VERSIONS_IDENTIFIED
743+ global IS_DJANGO18
744+ global IS_DJANGO19
745+ global IS_DJANGO19_OR_HIGHER
746+ if DJANGO_VERSIONS_IDENTIFIED == False :
747+ DJANGO_VERSIONS_IDENTIFIED = True
748+ try :
749+ import django
750+ version = django .VERSION
751+ IS_DJANGO18 = version [0 ] == 1 and version [1 ] == 8
752+ IS_DJANGO19 = version [0 ] == 1 and version [1 ] == 9
753+ IS_DJANGO19_OR_HIGHER = ((version [0 ] == 1 and version [1 ] >= 9 ) or version [0 ] > 1 )
754+ except :
755+ pass
683756 if frame .f_code .co_name == 'render' :
757+ origin = _get_template_file_name (frame )
758+ line = _get_template_line (frame )
759+ position = None
684760 self_obj = frame .f_locals .get ('self' , None )
685761 if self_obj is None :
686762 return None
687- name = type (self_obj ).__name__
688- if name in ('Template' , 'TextNode' ):
689- return None
690- source_obj = getattr (self_obj , 'source' , None )
691- if source_obj and hasattr (source_obj , '__len__' ) and len (source_obj ) == 2 :
692- return str (source_obj [0 ]), source_obj [1 ]
693-
694- token_obj = getattr (self_obj , 'token' , None )
695- if token_obj is None :
696- return None
697- template_obj = getattr (frame .f_locals .get ('context' , None ), 'template' , None )
698- if template_obj is None :
699- return None
700- template_name = getattr (template_obj , 'origin' , None )
701- position = getattr (token_obj , 'position' , None )
702- if template_name and position :
703- return str (template_name ), position
704763
764+ if self_obj is not None and hasattr (self_obj , 'token' ) and hasattr (self_obj .token , 'position' ):
765+ position = self_obj .token .position
766+
767+ if origin is not None and position is None :
768+ active_bps = DJANGO_BREAKPOINTS .get (origin .lower ())
769+ if active_bps is None :
770+ active_bps = DJANGO_TEMPLATES .get (origin .lower ())
771+ if active_bps is None :
772+ DJANGO_BREAKPOINTS [origin .lower ()] = active_bps = DjangoBreakpointInfo (origin .lower ())
773+ if active_bps is not None :
774+ if line is not None :
775+ position = active_bps .line_number_to_offset (line )
776+ if origin and position :
777+ return str (origin ), position , line
705778
706779 return None
707780
@@ -901,12 +974,15 @@ def handle_call(self, frame, arg):
901974 if DJANGO_BREAKPOINTS :
902975 source_obj = get_django_frame_source (frame )
903976 if source_obj is not None :
904- origin , (start , end ) = source_obj
977+ origin , (start , end ), lineNumber = source_obj
905978
906979 active_bps = DJANGO_BREAKPOINTS .get (origin .lower ())
907980 should_break = False
908- if active_bps is not None :
981+ if active_bps is not None and origin != '<unknown source>' :
909982 should_break , bkpt_id = active_bps .should_break (start , end )
983+ isPlainText = active_bps .is_range_plain_text (start , end )
984+ if isPlainText :
985+ should_break = False
910986 if should_break :
911987 probe_stack ()
912988 update_all_thread_stacks (self )
@@ -1464,7 +1540,7 @@ def get_frame_list(self):
14641540 frame_info = None
14651541
14661542 if source_obj is not None :
1467- origin , (start , end ) = source_obj
1543+ origin , (start , end ), lineNumber = source_obj
14681544
14691545 filename = str (origin )
14701546 bp_info = DJANGO_BREAKPOINTS .get (filename .lower ())
@@ -1622,6 +1698,7 @@ def __init__(self, conn):
16221698 to_bytes ('brka' ) : self .command_break_all ,
16231699 to_bytes ('resa' ) : self .command_resume_all ,
16241700 to_bytes ('rest' ) : self .command_resume_thread ,
1701+ to_bytes ('thrf' ) : self .command_get_thread_frames ,
16251702 to_bytes ('ares' ) : self .command_auto_resume ,
16261703 to_bytes ('exec' ) : self .command_execute_code ,
16271704 to_bytes ('chld' ) : self .command_enum_children ,
@@ -1805,6 +1882,13 @@ def command_break_all(self):
18051882 SEND_BREAK_COMPLETE = True
18061883 mark_all_threads_for_break ()
18071884
1885+ def command_get_thread_frames (self ):
1886+ tid = read_int (self .conn )
1887+ THREADS_LOCK .acquire ()
1888+ thread = THREADS [tid ]
1889+ THREADS_LOCK .release ()
1890+ thread .enum_thread_frames_locally ()
1891+
18081892 def command_resume_all (self ):
18091893 # resume all
18101894 THREADS_LOCK .acquire ()
@@ -2190,7 +2274,7 @@ def attach_process(port_num, debug_id, debug_options, currentPid, report = False
21902274 ## Begin modification by Don Jayamanne
21912275 # Pass current Process id to pass back to debugger
21922276 write_int (conn , currentPid ) # success
2193- ## End Modification by Don Jayamanne
2277+ ## End Modification by Don Jayamanne
21942278 break
21952279 except :
21962280 import time
@@ -2480,7 +2564,7 @@ def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script'
24802564 # Pass current Process id to pass back to debugger
24812565 attach_process (port_num , debug_id , debug_options , currentPid , report = True )
24822566 ## End Modification by Don Jayamanne
2483-
2567+
24842568 # setup the current thread
24852569 cur_thread = new_thread ()
24862570 cur_thread .stepping = STEPPING_LAUNCH_BREAK
@@ -2529,3 +2613,94 @@ def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script'
25292613 get_code (exec_code ),
25302614 get_code (new_thread_wrapper )
25312615))
2616+
2617+ ## Begin modification by Don Jayamanne
2618+ def _read_file (filename ):
2619+ f = open (filename , "r" )
2620+ s = f .read ()
2621+ f .close ()
2622+ return s
2623+
2624+
2625+ def _offset_to_line_number (text , offset ):
2626+ curLine = 1
2627+ curOffset = 0
2628+ while curOffset < offset :
2629+ if curOffset == len (text ):
2630+ return - 1
2631+ c = text [curOffset ]
2632+ if c == '\n ' :
2633+ curLine += 1
2634+ elif c == '\r ' :
2635+ curLine += 1
2636+ if curOffset < len (text ) and text [curOffset + 1 ] == '\n ' :
2637+ curOffset += 1
2638+
2639+ curOffset += 1
2640+
2641+ return curLine
2642+
2643+
2644+ def _get_source_django_18_or_lower (frame ):
2645+ # This method is usable only for the Django <= 1.8
2646+ try :
2647+ node = frame .f_locals ['self' ]
2648+ if hasattr (node , 'source' ):
2649+ return node .source
2650+ else :
2651+ if IS_DJANGO18 :
2652+ # The debug setting was changed since Django 1.8
2653+ print ("WARNING: Template path is not available. Set the 'debug' option in the OPTIONS of a DjangoTemplates "
2654+ "backend." )
2655+ else :
2656+ # The debug setting for Django < 1.8
2657+ print ("WARNING: Template path is not available. Please set TEMPLATE_DEBUG=True in your settings.py to make "
2658+ "django template breakpoints working" )
2659+ return None
2660+
2661+ except :
2662+ print (traceback .format_exc ())
2663+ return None
2664+
2665+ def _get_template_file_name (frame ):
2666+ try :
2667+ if IS_DJANGO19_OR_HIGHER :
2668+ # The Node source was removed since Django 1.9
2669+ if dict_contains (frame .f_locals , 'context' ):
2670+ context = frame .f_locals ['context' ]
2671+ if hasattr (context , 'template' ) and hasattr (context .template , 'origin' ) and \
2672+ hasattr (context .template .origin , 'name' ):
2673+ return context .template .origin .name
2674+ return None
2675+ source = _get_source_django_18_or_lower (frame )
2676+ if source is None :
2677+ print ("Source is None\n " )
2678+ return None
2679+ fname = source [0 ].name
2680+
2681+ if fname == '<unknown source>' :
2682+ print ("Source name is %s\n " + fname )
2683+ return None
2684+ else :
2685+ abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_file (fname )
2686+ return abs_path_real_path_and_base [1 ]
2687+ except :
2688+ print (traceback .format_exc ())
2689+ return None
2690+
2691+
2692+ def _get_template_line (frame ):
2693+ if IS_DJANGO19_OR_HIGHER :
2694+ # The Node source was removed since Django 1.9
2695+ self = frame .f_locals ['self' ]
2696+ if hasattr (self , 'token' ) and hasattr (self .token , 'lineno' ):
2697+ return self .token .lineno
2698+ else :
2699+ return None
2700+ source = _get_source_django_18_or_lower (frame )
2701+ file_name = _get_template_file_name (frame )
2702+ try :
2703+ return _offset_to_line_number (_read_file (file_name ), source [1 ][0 ])
2704+ except :
2705+ return None
2706+ ## End modification by Don Jayamanne
0 commit comments