File: ZopeScriptDebugServer.py

package info (click to toggle)
boa-constructor 0.3.0-3
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 8,188 kB
  • ctags: 8,857
  • sloc: python: 54,163; sh: 66; makefile: 36
file content (223 lines) | stat: -rw-r--r-- 8,743 bytes parent folder | download | duplicates (6)
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