Completed
Push — master ( 31b814...90c146 )
by Stephan
32s
created

TestFodder   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 6
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 10
wmc 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 2 1
1
"""Graphical exception handler for PyGTK applications
2
3
(c) 2003 Gustavo J A M Carneiro gjc at inescporto.pt
4
(c) 2004-2005 Filip Van Raemdonck
5
(c) 2009, 2011, 2017 Stephan Sokolow
6
7
http://www.daa.com.au/pipermail/pygtk/2003-August/005775.html
8
Message-ID: <[email protected]>
9
"The license is whatever you want."
10
11
Instructions: import gtkexcepthook; gtkexcepthook.enable()
12
13
Changes from Van Raemdonck version:
14
 - Switched from auto-enable to gtkexcepthook.enable() to silence PyFlakes
15
   false positives. (Borrowed naming convention from cgitb)
16
 - Split out traceback import to silence PyFlakes warning.
17
 - Started to resolve PyLint complaints
18
19
@todo: Polish this up to meet my code formatting and clarity standards.
20
@todo: Clean up the SMTP support. It's a mess.
21
@todo: Confirm there isn't any other generally-applicable information that
22
       could be included in the debugging dump.
23
@todo: Consider the pros and cons of offering a function which allows
24
       app-specific debugging information to be registered for inclusion.
25
"""
26
27
__author__ = "Filip Van Daemdonck"
28
__license__ = "whatever you want"
29
30
import inspect, linecache, pydoc, sys
31
# import traceback
32
from cStringIO import StringIO
33
from gettext import gettext as _
34
from pprint import pformat
35
from smtplib import SMTP
36
37
import pygtk
38
pygtk.require('2.0')
39
import gtk, pango
40
41
# TODO: Decide what to do with this
42
# def analyse(exctyp, value, tback):
43
#     trace = StringIO()
44
#     traceback.print_exception(exctyp, value, tback, None, trace)
45
#     return trace
46
47
def lookup(name, frame, lcls):
48
    # TODO: MyPy type signature
49
    '''Find the value for a given name in the given frame'''
50
    if name in lcls:
51
        return 'local', lcls[name]
52
    elif name in frame.f_globals:
53
        return 'global', frame.f_globals[name]
54
    elif '__builtins__' in frame.f_globals:
55
        builtins = frame.f_globals['__builtins__']
56
        if isinstance(builtins, dict):
57
            if name in builtins:
58
                return 'builtin', builtins[name]
59
        else:
60
            if hasattr(builtins, name):
61
                return 'builtin', getattr(builtins, name)
62
    return None, []
63
64
def analyse(exctyp, value, tback):
65
    # TODO: MyPy type signature
66
    import tokenize, keyword
67
68
    trace = StringIO()
69
    nlines = 3
70
    frecs = inspect.getinnerframes(tback, nlines)
71
    trace.write('Traceback (most recent call last):\n')
72
    # pylint: disable=unused-variable
73
    for frame, fname, lineno, funcname, context, cindex in frecs:
74
        trace.write('  File "%s", line %d, ' % (fname, lineno))
75
        args, varargs, varkw, lcls = inspect.getargvalues(frame)
76
77
        def readline(lno=[lineno], *args):
78
            if args:
79
                print args
80
            try:
81
                return linecache.getline(fname, lno[0])
82
            finally:
83
                lno[0] += 1
84
        _all, prev, name, scope = {}, None, '', None
85
        for ttype, tstr, stup, etup, lin in tokenize.generate_tokens(readline):
86
            if ttype == tokenize.NAME and tstr not in keyword.kwlist:
87
                if name:
88
                    if name[-1] == '.':
89
                        try:
90
                            val = getattr(prev, tstr)
91
                        except AttributeError:
92
                            # XXX skip the rest of this identifier only
93
                            break
94
                        name += tstr
95
                else:
96
                    assert not name and not scope
97
                    scope, val = lookup(tstr, frame, lcls)
98
                    name = tstr
99
                try:
100
                    if val:
101
                        prev = val
102
                except:
103
                    pass
104
                # TODO
105
                # print('  found', scope, 'name', name, 'val', val, 'in',
106
                #       prev, 'for token', tstr)
107
            elif tstr == '.':
108
                if prev:
109
                    name += '.'
110
            else:
111
                if name:
112
                    _all[name] = (scope, prev)
113
                prev, name, scope = None, '', None
114
                if ttype == tokenize.NEWLINE:
115
                    break
116
117
        trace.write(funcname +
118
          inspect.formatargvalues(args, varargs, varkw, lcls,
119
            formatvalue=lambda v: '=' + pydoc.text.repr(v)) + '\n')
120
        trace.write(''.join(['    ' + x.replace('\t', '  ')
121
                             for x in context if x.strip()]))
122
        if len(_all):
123
            trace.write('  variables: %s\n' % pformat(_all, indent=3))
124
125
    trace.write('%s: %s' % (exctyp.__name__, value))
126
    return trace
127
128
def _info(exctyp, value, tback):
129
    # TODO: MyPy type signature
130
    # pylint: disable=no-member
131
    trace = None
132
    dialog = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
133
                               buttons=gtk.BUTTONS_NONE)
134
    dialog.set_title(_("Bug Detected"))
135
    if gtk.check_version(2, 4, 0) is not None:
136
        dialog.set_has_separator(False)
137
138
    primary = _("<big><b>A programming error has been detected during the "
139
                "execution of this program.</b></big>")
140
    secondary = _("It probably isn't fatal, but should be reported to the "
141
                  "developers nonetheless.")
142
143
    try:
144
        # TODO: Refactor to not use a try/except
145
        email = feedback  # pylint: disable=undefined-variable
146
        dialog.add_button(_("Report..."), 3)
147
    except NameError:
148
        secondary += _("\n\nPlease remember to include the contents of the "
149
                       "Details dialog.")
150
        # could ask for an email address instead...
151
152
    try:
153
        setsec = dialog.format_secondary_text
154
    except AttributeError:
155
        raise
156
        # TODO
157
        # dialog.vbox.get_children()[0].get_children()[1].set_markup(
158
        #    '%s\n\n%s' % (primary, secondary))
159
        # lbl.set_property("use-markup", True)
160
    else:
161
        del setsec
162
        dialog.set_markup(primary)
163
        dialog.format_secondary_text(secondary)
164
165
    dialog.add_button(_("Details..."), 2)
166
    dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
167
    dialog.add_button(gtk.STOCK_QUIT, 1)
168
169
    while True:
170
        resp = dialog.run()
171
        if resp == 3:
172
            if trace is None:
173
                trace = analyse(exctyp, value, tback)
174
175
            # TODO: prettyprint, deal with problems in sending feedback, &tc
176
            # TODO: Refactor to not use a try/except
177
            try:
178
                server = smtphost  # pylint: disable=undefined-variable
179
            except NameError:
180
                server = 'localhost'
181
182
            message = ('From: buggy_application"\nTo: bad_programmer\n'
183
                'Subject: Exception feedback\n\n%s' % trace.getvalue())
184
185
            smtp = SMTP()
186
            smtp.connect(server)
187
            smtp.sendmail(email, (email,), message)
188
            smtp.quit()
189
            break
190
191
        elif resp == 2:
192
            if trace is None:
193
                trace = analyse(exctyp, value, tback)
194
195
            # Show details...
196
            details = gtk.Dialog(_("Bug Details"), dialog,
197
              gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
198
              (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE, ))
199
            details.set_property("has-separator", False)
200
201
            textview = gtk.TextView()
202
            textview.show()
203
            textview.set_editable(False)
204
            textview.modify_font(pango.FontDescription("Monospace"))
205
206
            swin = gtk.ScrolledWindow()
207
            swin.show()
208
            swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
209
            swin.add(textview)
210
            details.vbox.add(swin)
211
            textbuffer = textview.get_buffer()
212
            textbuffer.set_text(trace.getvalue())
213
214
            monitor = gtk.gdk.screen_get_default().get_monitor_at_window(
215
                dialog.window)
216
            area = gtk.gdk.screen_get_default().get_monitor_geometry(monitor)
217
            try:
218
                w = area.width // 1.6
219
                h = area.height // 1.6
220
            except SyntaxError:
221
                # python < 2.2
222
                w = area.width / 1.6
223
                h = area.height / 1.6
224
            details.set_default_size(int(w), int(h))
225
226
            details.run()
227
            details.destroy()
228
229
        elif resp == 1 and gtk.main_level() > 0:
230
            gtk.main_quit()
231
            break
232
        else:
233
            break
234
235
    dialog.destroy()
236
237
def enable():  # type: () -> None
238
    """Call this to set gtkexcepthook as the default exception handler"""
239
    sys.excepthook = _info
240
241
242
if __name__ == '__main__':
243
    class TestFodder(object):  # pylint: disable=too-few-public-methods
244
        """Just something interesting to show in the augmented traceback"""
245
        y = 'Test'
246
247
        def __init__(self): # type: () -> None
248
            self.z = self  # pylint: disable=invalid-name
249
    x = TestFodder()
250
    w = ' e'
251
252
    # TODO: Refactor this
253
    # feedback = '[email protected]'
254
    # smtphost = 'mx.bigcorp.comp'
255
    # 1, x.z.y, f, w
256
257
    enable()
258
    raise Exception(x.z.y + w)
259
260
# vim: set sw=4 sts=4 :
261