1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
from IsolatedDebugger import DebugServer
__traceable__ = 0
TAL_INTERP_MODULE_NAME = 'TAL.TALInterpreter'
TALES_MODULE_NAME = 'Products.PageTemplates.TALES'
isAPythonScriptMetaType = {
'Script (Python)': 1,
}.has_key
def isATALInterpeterFrame(frame):
"""Indicates whether the given frame should show up as a TAL frame.
A TAL frame is a call to interpret() from the __call__(),
do_useMacro(), or do_defineSlot() methods. This depends a lot
on specific code in TAL. :-(
"""
if (frame.f_code.co_name == 'interpret' and
frame.f_globals.get('__name__') == TAL_INTERP_MODULE_NAME):
caller = frame.f_back
if caller.f_globals.get('__name__') == TAL_INTERP_MODULE_NAME:
caller_name = caller.f_code.co_name
if caller_name in ('__call__', 'do_useMacro', 'do_defineSlot'):
return 1
return 0
class ZopeScriptDebugServer(DebugServer):
"""A debug server that facilitates debugging of Zope Python Scripts
and Page Templates.
"""
# scripts_only_mode is turned on to stop only in through-the-web scripts.
scripts_only_mode = 0
stack_extra = None
def beforeResume(self):
"""Frees references before jumping back into user code."""
DebugServer.beforeResume(self)
self.stack_extra = None
def getFilenameAndLine(self, frame):
"""Returns the filename and line number for the frame. Invoked often.
This implementation adjusts for Python scripts and page templates.
"""
code = frame.f_code
filename = code.co_filename
lineno = frame.f_lineno
# XXX Python scripts currently (Zope 2.5) use their meta type
# as their filename. This is efficient but brittle.
if isAPythonScriptMetaType(filename):
meta_type = filename
# XXX This assumes the user never changes the "script" binding.
script = frame.f_globals.get('script', None)
if script is not None:
url = script.absolute_url()
url = url.split('://', 1)[-1]
filename = 'zopedebug://%s/%s' % (url, meta_type)
# Offset for Boa's purposes
lineno = lineno + 1
return filename, lineno
if code.co_name == 'interpret' and isATALInterpeterFrame(frame):
source_file, ln = self.getTALPosition(frame)
if source_file:
return self.TALSourceToURL(source_file, frame), ln
return self.canonic(filename), lineno
def TALSourceToURL(self, source_file, frame):
if source_file.startswith('traversal:'):
path = source_file[10:]
if path.startswith('/'):
path = path[1:]
meta_type = 'Page Template'
host = 'localhost:8080' # XXX XXX!
return 'zopedebug://%s/%s/%s' % (host, path, meta_type)
elif source_file.startswith('/'):
meta_type = 'Page Template'
interp = frame.f_locals.get('self')
if interp is not None:
global_vars = getattr(interp.engine, 'global_vars', {})
template = global_vars.get('template', None)
if template:
url = template.absolute_url().split('://', 1)[-1]
return 'zopedebug://%s/Page Template'%url
return source_file # TODO: something better
def getTALPosition(self, frame):
"""If the frame is in TALInterpreter.interpret(), detects what
template was being interpreted and where, but only for specific
interpreter frames. Returns the source file and line number.
"""
se = self.stack_extra
if se:
info = se.get(frame, None)
if info:
# Return the precomputed file and lineno.
source_file, lineno = info
return source_file, lineno
# Inspect TAL frames. XXX brittle in many ways.
interp = frame.f_locals.get('self', None)
source_file = interp.sourceFile
position = interp.position
if position:
lineno = position[0] or 0
else:
lineno = 0
return source_file, lineno
def getFrameNames(self, frame):
"""Returns the module and function name for the frame.
"""
if isATALInterpeterFrame(frame):
source_file, ln = self.getTALPosition(frame)
if source_file:
return '', source_file.split('/')[-1]
return DebugServer.getFrameNames(self, frame)
def isTraceable(self, frame):
"""Indicates whether the debugger should step into the given frame.
Called often.
"""
if self.scripts_only_mode:
code = frame.f_code
if isAPythonScriptMetaType(code.co_filename):
return 1
if code.co_name == 'setPosition':
if frame.f_globals.get('__name__') == TALES_MODULE_NAME:
# Trace calls to PageTemplate.TALES.Context.setPosition().
# Avoid stopping more than once per call.
if frame.f_lineno == frame.f_code.co_firstlineno:
return 1
return 0
return DebugServer.isTraceable(self, frame)
def isAScriptFrame(self, frame):
"""Indicates whether the given frame is a high-level script frame.
"""
if isAPythonScriptMetaType(frame.f_code.co_filename):
return 1
if isATALInterpeterFrame(frame):
return 1
return 0
def getStackInfo(self):
"""Returns a tuple describing the current stack.
"""
exc_type, exc_value, stack, frame_stack_len = (
DebugServer.getStackInfo(self))
if self.scripts_only_mode:
# Filter non-script frames out of the stack.
new_stack = []
new_len = 0
for idx in range(len(stack)):
frame, lineno = stack[idx]
if self.isAScriptFrame(frame):
new_stack.append((frame, lineno))
if idx < frame_stack_len:
new_len = new_len + 1
stack = new_stack
frame_stack_len = new_len
# Compute filenames and positions for TAL frames.
# The source_file and position variables get applied to the
# interpreter frame that called them.
self.stack_extra = {}
last_interp = None
saved_source = None
saved_lineno = None
for idx in range(len(stack) - 1, -1, -1):
frame, lineno = stack[idx]
if isATALInterpeterFrame(frame):
caller = frame.f_back
caller_name = caller.f_code.co_name
interp = frame.f_locals.get('self', None)
if last_interp is interp:
self.stack_extra[frame] = (saved_source, saved_lineno)
if caller_name in ('do_useMacro', 'do_defineSlot'):
# Using a macro or slot.
# Expect to find saved_source and saved_position in
# locals.
saved_source = caller.f_locals.get('prev_source') #saved_source
position = 0#caller.f_locals.get('saved_position')
if position:
saved_lineno = position[0] or 0
else:
saved_lineno = 0
last_interp = interp
return exc_type, exc_value, stack, frame_stack_len
def afterBreakpoint(self, frame):
# Set a default stepping mode.
self.set_step()
# Choose scripts_only_mode based on whether the hard break was in
# a script or not.
if self.isAScriptFrame(frame):
self.scripts_only_mode = 1
else:
self.scripts_only_mode = 0
def getFrameNamespaces(self, frame):
"""Returns the locals and globals for a frame.
"""
if isATALInterpeterFrame(frame):
# This is a TAL interpret() frame. Use special locals
# and globals.
interp = frame.f_locals.get('self')
if interp is not None:
local_vars = getattr(interp.engine, 'local_vars', {})
global_vars = getattr(interp.engine, 'global_vars', {})
return global_vars, local_vars
return frame.f_globals, frame.f_locals
|