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
|