]>
Commit | Line | Data |
---|---|---|
d14a1e28 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: dbg.py | |
3 | # RCS-ID: $Id$ | |
4 | # Author: Will Sadkin | |
5 | # Email: wsadkin@nameconnector.com | |
6 | # Created: 07/11/2002 | |
7 | # Copyright: (c) 2002 by Will Sadkin, 2002 | |
8 | # License: wxWindows license | |
9 | #---------------------------------------------------------------------------- | |
d4b73b1b RD |
10 | # 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
11 | # | |
12 | # o V2.5 compatability update | |
13 | # | |
1fded56b | 14 | |
d14a1e28 RD |
15 | """ |
16 | This module provides a useful debugging framework that supports | |
17 | showing nesting of function calls and allows a program to contain | |
18 | lots of debugging print statements that can easily be turned on | |
19 | or off to debug the code. It also supports the ability to | |
20 | have each function indent the debugging statements contained | |
21 | within it, including those of any other function called within | |
22 | its scope, thus allowing you to see in what order functions are | |
23 | being called, and from where. | |
24 | ||
25 | This capability is particularly useful in wxPython applications, | |
26 | where exactly events occur that cause functions to be called is | |
27 | not entirely clear, and because wxPython programs can't be run | |
28 | from inside other debugging environments that have their own | |
29 | message loops. | |
30 | ||
31 | This module defines a Logger class, responsible for managing | |
32 | debugging output. Each Logger instance can be given a name | |
33 | at construction; if this is done, '<name>:' will precede each | |
34 | logging output made by that Logger instance. | |
35 | ||
36 | The log() function this class provides takes a set of positional | |
37 | arguments that are printed in order if debugging is enabled | |
38 | (just like print does), followed by a set of keyword arguments | |
39 | that control the behavior of the log() function itself on subsequent | |
40 | calls. The current keyword arguments are: | |
41 | ||
42 | indent | |
43 | When set to a value of 1, this increments the current | |
44 | indentation level, causing all subsequent dbg() outputs to be | |
45 | indented by 3 more spaces. When set to a value of 0, | |
46 | this process is reversed, causing the indent to decrease by | |
47 | 3 spaces. The default indentation level is 0. | |
48 | ||
49 | enable | |
50 | When set to a value of 1, this turns on dbg() output for | |
51 | for program importing this module, until told to do otherwise. | |
52 | When set to a value of 0, dbg output is turned off. (dbg | |
53 | output is off by default.) | |
54 | ||
55 | suspend | |
56 | When set to a value of 1, this increments the current | |
57 | "suspension" level. This makes it possible for a function | |
58 | to temporarily suspend its and any of its dependents' | |
59 | potential outputs that use the same Logger instance. | |
60 | When set to a value of 0, the suspension level is | |
61 | decremented. When the value goes back to 0, potential | |
62 | logging is resumed (actual output depends on the | |
63 | "enable" status of the Logger instance in question.) | |
64 | ||
65 | wxlog | |
66 | When set to a value of 1, the output will be sent to the | |
67 | active wxLog target. | |
68 | ||
69 | stream | |
70 | When set to a non-None value, the current output stream | |
71 | (default of sys.stdout) is pushed onto a stack of streams, | |
72 | and is replaced in the dbg system with the specified stream. | |
73 | When called with a value of None, the previous stream will | |
74 | be restored (if stacked.) If set to None without previously | |
75 | changing it will result in no action being taken. | |
76 | ||
77 | You can also call the log function implicitly on the Logger | |
9f4cc34f RD |
78 | instance, ie. you can type:: |
79 | ||
d14a1e28 RD |
80 | from wxPython.tools.dbg import Logger |
81 | dbg = Logger() | |
82 | dbg('something to print') | |
83 | ||
84 | Using this fairly simple mechanism, it is possible to get fairly | |
85 | useful debugging output in a program. Consider the following | |
86 | code example: | |
87 | ||
88 | >>> d = {1:'a', 2:'dictionary', 3:'of', 4:'words'} | |
89 | >>> dbg = dbg.Logger('module') | |
90 | >>> dbg(enable=1) | |
91 | module: dbg enabled | |
92 | >>> def foo(d): | |
93 | ... dbg('foo', indent=1) | |
94 | ... bar(d) | |
95 | ... dbg('end of foo', indent=0) | |
96 | ... | |
97 | >>> def bar(d): | |
98 | ... dbg('bar', indent=1) | |
99 | ... dbg('contents of d:', indent=1) | |
100 | ... l = d.items() | |
101 | ... l.sort() | |
102 | ... for key, value in l: | |
103 | ... dbg('%d =' % key, value) | |
104 | ... dbg(indent=0) | |
105 | ... dbg('end of bar', indent=0) | |
106 | ... | |
107 | >>> foo(d) | |
108 | module: foo | |
109 | module: bar | |
110 | module: contents of d: | |
111 | module: 1 = a | |
112 | module: 2 = dictionary | |
113 | module: 3 = of | |
114 | module: 4 = words | |
115 | module: end of bar | |
116 | module: end of foo | |
117 | >>> | |
118 | ||
119 | """ | |
120 | ||
121 | ||
122 | class Logger: | |
123 | def __init__(self, name=None): | |
124 | import sys | |
125 | self.name = name | |
126 | self._indent = 0 # current number of indentations | |
127 | self._dbg = 0 # enable/disable flag | |
128 | self._suspend = 0 # allows code to "suspend/resume" potential dbg output | |
129 | self._wxLog = 0 # use wxLogMessage for debug output | |
130 | self._outstream = sys.stdout # default output stream | |
131 | self._outstream_stack = [] # for restoration of streams as necessary | |
132 | ||
133 | ||
134 | def IsEnabled(): | |
135 | return self._dbg | |
136 | ||
137 | def IsSuspended(): | |
138 | return _suspend | |
139 | ||
140 | ||
141 | def log(self, *args, **kwargs): | |
142 | """ | |
143 | This function provides a useful framework for generating | |
144 | optional debugging output that can be displayed at an | |
145 | arbitrary level of indentation. | |
146 | """ | |
147 | if not self._dbg and not 'enable' in kwargs.keys(): | |
148 | return | |
149 | ||
150 | if self._dbg and len(args) and not self._suspend: | |
f54a36bb RD |
151 | # (emulate print functionality; handle unicode as best as possible:) |
152 | strs = [] | |
153 | for arg in args: | |
154 | try: | |
155 | strs.append(str(arg)) | |
156 | except: | |
157 | strs.append(repr(arg)) | |
158 | ||
d14a1e28 RD |
159 | output = ' '.join(strs) |
160 | if self.name: output = self.name+': ' + output | |
161 | output = ' ' * 3 * self._indent + output | |
162 | ||
163 | if self._wxLog: | |
164 | from wxPython.wx import wxLogMessage # (if not already imported) | |
165 | wxLogMessage(output) | |
166 | else: | |
167 | self._outstream.write(output + '\n') | |
168 | self._outstream.flush() | |
169 | # else do nothing | |
170 | ||
171 | # post process args: | |
172 | for kwarg, value in kwargs.items(): | |
173 | if kwarg == 'indent': | |
174 | self.SetIndent(value) | |
175 | elif kwarg == 'enable': | |
176 | self.SetEnabled(value) | |
177 | elif kwarg == 'suspend': | |
178 | self.SetSuspend(value) | |
179 | elif kwarg == 'wxlog': | |
180 | self.SetWxLog(value) | |
181 | elif kwarg == 'stream': | |
182 | self.SetStream(value) | |
183 | ||
184 | # aliases for the log function | |
185 | dbg = log # backwards compatible | |
186 | msg = log # | |
187 | __call__ = log # this one lets you 'call' the instance directly | |
188 | ||
189 | ||
190 | def SetEnabled(self, value): | |
191 | if value: | |
192 | old_dbg = self._dbg | |
193 | self._dbg = 1 | |
194 | if not old_dbg: | |
195 | self.dbg('dbg enabled') | |
196 | else: | |
197 | if self._dbg: | |
198 | self.dbg('dbg disabled') | |
199 | self._dbg = 0 | |
200 | ||
201 | ||
202 | def SetSuspend(self, value): | |
203 | if value: | |
204 | self._suspend += 1 | |
205 | elif self._suspend > 0: | |
206 | self._suspend -= 1 | |
207 | ||
208 | ||
209 | def SetIndent(self, value): | |
210 | if value: | |
211 | self._indent += 1 | |
212 | elif self._indent > 0: | |
213 | self._indent -= 1 | |
214 | ||
215 | ||
216 | def SetWxLog(self, value): | |
217 | self._wxLog = value | |
218 | ||
219 | ||
220 | def SetStream(self, value): | |
221 | if value: | |
222 | self._outstream_stack.append( self._outstream ) | |
223 | self._outstream = value | |
224 | elif value is None and len(self._outstream_stack) > 0: | |
225 | self._outstream = self._outstream_stack.pop(-1) | |
226 | ||
227 | ||
228 | #------------------------------------------------------------ | |
229 | ||
230 | if __name__ == "__main__": | |
d4b73b1b RD |
231 | import sys |
232 | import wx | |
233 | ||
234 | wx.Log_SetActiveTarget( wx.LogStderr() ) | |
d14a1e28 RD |
235 | logger = Logger('module') |
236 | dbg = logger.dbg | |
237 | dbg(enable=1) | |
238 | logger('test __call__ interface') | |
239 | dbg('testing wxLog output to stderr:', wxlog=1, indent=1) | |
240 | dbg('1,2,3...') | |
d4b73b1b RD |
241 | dbg('testing wx.LogNull:') |
242 | devnull = wx.LogNull() | |
d14a1e28 RD |
243 | dbg('4,5,6...') # shouldn't print, according to doc... |
244 | del devnull | |
d4b73b1b RD |
245 | dbg('(resuming to wx.LogStdErr)', '7,8,9...', indent=0) |
246 | dbg('disabling wx.Log output, switching to stderr:') | |
d14a1e28 RD |
247 | dbg(wxlog=0, stream=sys.stderr) |
248 | dbg(logger._outstream, 'switching back to stdout:') | |
249 | dbg(stream=None) | |
250 | dbg(logger._outstream ) | |
251 | def foo(str): | |
252 | dbg('foo:', indent=1) | |
253 | dbg(str, indent=0) | |
254 | foo('testing dbg inside function') | |
255 | class bar(Logger): | |
256 | def __init__(self, name): | |
257 | Logger.__init__(self, name) | |
258 | def enable(self, value): | |
259 | self.dbg(enable=value) | |
260 | def foo(self, str): | |
261 | self.dbg('foo:', indent=1) | |
262 | self.dbg(str, indent=0) | |
263 | f = bar('class mixin') | |
264 | f.foo("shouldn't print") | |
265 | f.enable(1) | |
266 | f.foo("should print") | |
267 | dbg('test completed.', enable=0) | |
268 | dbg('(double-checking ;-)') | |
1fded56b | 269 |