dev_tool.py
11.3 KB
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
###############################################################################
# Name: dev_tool.py #
# Purpose: Provides logging and error tracking utilities #
# Author: Cody Precord <cprecord@editra.org> #
# Copyright: (c) 2008 Cody Precord <staff@editra.org> #
# License: wxWindows License #
###############################################################################
""" Editra Development Tools
Tools and Utilities for debugging and helping with development of Editra.
@summary: Utility function for debugging the editor
"""
__author__ = "Cody Precord <cprecord@editra.org>"
__svnid__ = "$Id: dev_tool.py 72623 2012-10-06 19:33:06Z CJP $"
__revision__ = "$Revision: 72623 $"
#-----------------------------------------------------------------------------#
# Imports
import os
import sys
import re
import traceback
import time
import urllib2
import webbrowser
import codecs
import locale
import wx
# Editra Libraries
import ed_glob
import ed_msg
import eclib
from ebmlib import IsUnicode, LogFile
#-----------------------------------------------------------------------------#
# Globals
_ = wx.GetTranslation
RE_LOG_LBL = re.compile(r"\[(.+?)\]")
# The default fallback encoding
DEFAULT_ENCODING = locale.getpreferredencoding()
try:
codecs.lookup(DEFAULT_ENCODING)
except (LookupError, TypeError):
DEFAULT_ENCODING = 'utf-8'
PYTHONW = 'pythonw' in sys.executable.lower()
#-----------------------------------------------------------------------------#
# General Debugging Helper Functions
def DEBUGP(statement, *args):
"""Prints debug messages and broadcasts them on the log message channel.
Subscribing a listener with any of the EDMSG_LOG_* types will recieve its
messages from this method.
1. Formatting
- [object/module name][msg_type] message string
2. Message Type:
- [err] : Notes an exception or error condition (high priority)
- [warn] : Notes a error that is not severe (medium priority)
- [info] : General information message (normal priority)
- [evt] : Event related message (normal priority)
Example:
>>> DEBUGP("[ed_main][err] File failed to open")
@param statement: Should be a formatted string that starts with two
identifier blocks. The first is used to indicate the
source of the message and is used as the primary means
of filtering. The second block is the type of message,
this is used to indicate the priority of the message and
is used as the secondary means of filtering.
"""
# Check if formatting should be done here
if len(args):
try:
statement = statement % args
except:
pass
# Create a LogMsg object from the statement string
lbls = [lbl.strip() for lbl in RE_LOG_LBL.findall(statement)]
info = RE_LOG_LBL.sub('', statement, 2).rstrip()
if len(lbls) > 1:
msg = LogMsg(info, lbls[0], lbls[1])
elif len(lbls) == 1:
msg = LogMsg(info, lbls[0])
else:
msg = LogMsg(info)
# Only print to stdout when DEBUG is active
# Cant print to stdio if using pythonw
msg_type = msg.Type
if ed_glob.DEBUG:
mstr = unicode(msg)
mstr = mstr.encode('utf-8', 'replace')
if not PYTHONW:
print(mstr)
# Write to log file
logfile = EdLogFile()
logfile.WriteMessage(mstr)
# Check for trapped exceptions to print
if ed_glob.VDEBUG and msg_type in ('err', 'error'):
traceback.print_exc()
logfile.WriteMessage(traceback.format_exc())
# Dispatch message to all observers
if msg_type in ('err', 'error'):
mtype = ed_msg.EDMSG_LOG_ERROR
if ed_glob.VDEBUG:
msg = LogMsg(msg.Value + os.linesep + traceback.format_exc(),
msg.Origin, msg.Type)
elif msg_type in ('warn', 'warning'):
mtype = ed_msg.EDMSG_LOG_WARN
elif msg_type in ('evt', 'event'):
mtype = ed_msg.EDMSG_LOG_EVENT
elif msg.Type in ('info', 'information'):
mtype = ed_msg.EDMSG_LOG_INFO
else:
mtype = ed_msg.EDMSG_LOG_ALL
ed_msg.PostMessage(mtype, msg)
#-----------------------------------------------------------------------------#
class LogMsg(object):
"""LogMsg is a container class for representing log messages. Converting
it to a string will yield a formatted log message with timestamp. Once a
message has been displayed once (converted to a string) it is marked as
being expired.
"""
def __init__(self, msg, msrc=u"unknown", level=u"info"):
"""Create a LogMsg object
@param msg: the log message string
@keyword msrc: Source of message
@keyword level: Priority of the message
"""
assert isinstance(msg, basestring)
assert isinstance(msrc, basestring)
assert isinstance(level, basestring)
super(LogMsg, self).__init__()
# Attributes
self._msg = dict(mstr=DecodeString(msg),
msrc=DecodeString(msrc),
lvl=DecodeString(level),
tstamp=time.time())
self._ok = True
def __eq__(self, other):
"""Define the equal to operation"""
return self.TimeStamp == other.TimeStamp
def __ge__(self, other):
"""Define the greater than or equal to operation"""
return self.TimeStamp >= other.TimeStamp
def __gt__(self, other):
"""Define the greater than operation"""
return self.TimeStamp > other.TimeStamp
def __le__(self, other):
"""Define the less than or equal to operation"""
return self.TimeStamp <= other.TimeStamp
def __lt__(self, other):
"""Define the less than operation"""
return self.TimeStamp < other.TimeStamp
def __repr__(self):
"""String representation of the object"""
return '<LogMsg %s:%d>' % (self._msg['lvl'], self._msg['tstamp'])
def __str__(self):
"""Returns a nice formatted string version of the message"""
s_lst = [u"[%s][%s][%s]%s" % (self.ClockTime, self.Origin,
self.Type, msg.rstrip())
for msg in self.Value.split(u"\n")
if len(msg.strip())]
try:
sys_enc = sys.getfilesystemencoding()
out = os.linesep.join([val.encode(sys_enc, 'replace')
for val in s_lst])
except UnicodeEncodeError:
out = repr(self)
# Mark Message as have being fetched (expired)
self._ok = False
return out
def __unicode__(self):
"""Convert to unicode"""
rval = u""
try:
sval = str(self)
rval = sval.decode(sys.getfilesystemencoding(), 'replace')
except UnicodeDecodeError, msg:
pass
return rval
@property
def ClockTime(self):
"""Formatted timestring of the messages timestamp"""
ltime = time.localtime(self._msg['tstamp'])
tstamp = u"%s:%s:%s" % (str(ltime[3]).zfill(2),
str(ltime[4]).zfill(2),
str(ltime[5]).zfill(2))
return tstamp
@property
def Expired(self):
"""Has this message already been retrieved"""
return not self._ok
@property
def Origin(self):
"""Where the message came from"""
return self._msg['msrc']
@property
def TimeStamp(self):
"""Property for accessing timestamp"""
return self._msg['tstamp']
@property
def Type(self):
"""The messages level type"""
return self._msg['lvl']
@property
def Value(self):
"""Returns the message part of the log string"""
return self._msg['mstr']
#-----------------------------------------------------------------------------#
class EdLogFile(LogFile):
"""Transient log file object"""
def __init__(self):
super(EdLogFile, self).__init__("editra")
def PurgeOldLogs(self, days):
try:
super(EdLogFile, self).PurgeOldLogs(days)
except OSError, msg:
DEBUGP("[dev_tool][err] PurgeOldLogs: %s" % msg)
#-----------------------------------------------------------------------------#
def DecodeString(string, encoding=None):
"""Decode the given string to Unicode using the provided
encoding or the DEFAULT_ENCODING if None is provided.
@param string: string to decode
@keyword encoding: encoding to decode string with
"""
if encoding is None:
encoding = DEFAULT_ENCODING
if not IsUnicode(string):
try:
rtxt = codecs.getdecoder(encoding)(string)[0]
except Exception, msg:
rtxt = string
return rtxt
else:
# The string is already Unicode so just return it
return string
#-----------------------------------------------------------------------------#
class EdErrorDialog(eclib.ErrorDialog):
"""Error reporter dialog"""
def __init__(self, msg):
super(EdErrorDialog, self).__init__(None, title="Error Report",
message=msg)
# Setup
self.SetDescriptionLabel(_("Error: Something unexpected hapend\n"
"Help improve Editra by clicking on "
"Report Error\nto send the Error "
"Traceback shown below."))
def Abort(self):
"""Abort the application"""
# Try a nice shutdown first time through
wx.CallLater(500, wx.GetApp().OnExit,
wx.MenuEvent(wx.wxEVT_MENU_OPEN, ed_glob.ID_EXIT),
True)
def GetProgramName(self):
"""Get the program name to display in error report"""
return "%s Version: %s" % (ed_glob.PROG_NAME, ed_glob.VERSION)
def Send(self):
"""Send the error report"""
msg = "mailto:%s?subject=Error Report&body=%s"
addr = "bugs@%s" % (ed_glob.HOME_PAGE.replace("http://", '', 1))
if wx.Platform != '__WXMAC__':
body = urllib2.quote(self.err_msg)
else:
body = self.err_msg
msg = msg % (addr, body)
msg = msg.replace("'", '')
webbrowser.open(msg)
#-----------------------------------------------------------------------------#
def ExceptionHook(exctype, value, trace):
"""Handler for all unhandled exceptions
@param exctype: Exception Type
@param value: Error Value
@param trace: Trace back info
"""
# Format the traceback
exc = traceback.format_exception(exctype, value, trace)
exc.insert(0, u"*** %s ***%s" % (eclib.TimeStamp(), os.linesep))
ftrace = u"".join(exc)
# Ensure that error gets raised to console as well
print ftrace
# If abort has been set and we get here again do a more forcefull shutdown
if EdErrorDialog.ABORT:
os._exit(1)
# Prevent multiple reporter dialogs from opening at once
if not EdErrorDialog.REPORTER_ACTIVE and not EdErrorDialog.ABORT:
dlg = EdErrorDialog(ftrace)
dlg.ShowModal()
dlg.Destroy()
#-----------------------------------------------------------------------------#