]>
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 | |
78 | instance, ie. you can type: | |
79 | from wxPython.tools.dbg import Logger | |
80 | dbg = Logger() | |
81 | dbg('something to print') | |
82 | ||
83 | Using this fairly simple mechanism, it is possible to get fairly | |
84 | useful debugging output in a program. Consider the following | |
85 | code example: | |
86 | ||
87 | >>> d = {1:'a', 2:'dictionary', 3:'of', 4:'words'} | |
88 | >>> dbg = dbg.Logger('module') | |
89 | >>> dbg(enable=1) | |
90 | module: dbg enabled | |
91 | >>> def foo(d): | |
92 | ... dbg('foo', indent=1) | |
93 | ... bar(d) | |
94 | ... dbg('end of foo', indent=0) | |
95 | ... | |
96 | >>> def bar(d): | |
97 | ... dbg('bar', indent=1) | |
98 | ... dbg('contents of d:', indent=1) | |
99 | ... l = d.items() | |
100 | ... l.sort() | |
101 | ... for key, value in l: | |
102 | ... dbg('%d =' % key, value) | |
103 | ... dbg(indent=0) | |
104 | ... dbg('end of bar', indent=0) | |
105 | ... | |
106 | >>> foo(d) | |
107 | module: foo | |
108 | module: bar | |
109 | module: contents of d: | |
110 | module: 1 = a | |
111 | module: 2 = dictionary | |
112 | module: 3 = of | |
113 | module: 4 = words | |
114 | module: end of bar | |
115 | module: end of foo | |
116 | >>> | |
117 | ||
118 | """ | |
119 | ||
120 | ||
121 | class Logger: | |
122 | def __init__(self, name=None): | |
123 | import sys | |
124 | self.name = name | |
125 | self._indent = 0 # current number of indentations | |
126 | self._dbg = 0 # enable/disable flag | |
127 | self._suspend = 0 # allows code to "suspend/resume" potential dbg output | |
128 | self._wxLog = 0 # use wxLogMessage for debug output | |
129 | self._outstream = sys.stdout # default output stream | |
130 | self._outstream_stack = [] # for restoration of streams as necessary | |
131 | ||
132 | ||
133 | def IsEnabled(): | |
134 | return self._dbg | |
135 | ||
136 | def IsSuspended(): | |
137 | return _suspend | |
138 | ||
139 | ||
140 | def log(self, *args, **kwargs): | |
141 | """ | |
142 | This function provides a useful framework for generating | |
143 | optional debugging output that can be displayed at an | |
144 | arbitrary level of indentation. | |
145 | """ | |
146 | if not self._dbg and not 'enable' in kwargs.keys(): | |
147 | return | |
148 | ||
149 | if self._dbg and len(args) and not self._suspend: | |
f54a36bb RD |
150 | # (emulate print functionality; handle unicode as best as possible:) |
151 | strs = [] | |
152 | for arg in args: | |
153 | try: | |
154 | strs.append(str(arg)) | |
155 | except: | |
156 | strs.append(repr(arg)) | |
157 | ||
d14a1e28 RD |
158 | output = ' '.join(strs) |
159 | if self.name: output = self.name+': ' + output | |
160 | output = ' ' * 3 * self._indent + output | |
161 | ||
162 | if self._wxLog: | |
163 | from wxPython.wx import wxLogMessage # (if not already imported) | |
164 | wxLogMessage(output) | |
165 | else: | |
166 | self._outstream.write(output + '\n') | |
167 | self._outstream.flush() | |
168 | # else do nothing | |
169 | ||
170 | # post process args: | |
171 | for kwarg, value in kwargs.items(): | |
172 | if kwarg == 'indent': | |
173 | self.SetIndent(value) | |
174 | elif kwarg == 'enable': | |
175 | self.SetEnabled(value) | |
176 | elif kwarg == 'suspend': | |
177 | self.SetSuspend(value) | |
178 | elif kwarg == 'wxlog': | |
179 | self.SetWxLog(value) | |
180 | elif kwarg == 'stream': | |
181 | self.SetStream(value) | |
182 | ||
183 | # aliases for the log function | |
184 | dbg = log # backwards compatible | |
185 | msg = log # | |
186 | __call__ = log # this one lets you 'call' the instance directly | |
187 | ||
188 | ||
189 | def SetEnabled(self, value): | |
190 | if value: | |
191 | old_dbg = self._dbg | |
192 | self._dbg = 1 | |
193 | if not old_dbg: | |
194 | self.dbg('dbg enabled') | |
195 | else: | |
196 | if self._dbg: | |
197 | self.dbg('dbg disabled') | |
198 | self._dbg = 0 | |
199 | ||
200 | ||
201 | def SetSuspend(self, value): | |
202 | if value: | |
203 | self._suspend += 1 | |
204 | elif self._suspend > 0: | |
205 | self._suspend -= 1 | |
206 | ||
207 | ||
208 | def SetIndent(self, value): | |
209 | if value: | |
210 | self._indent += 1 | |
211 | elif self._indent > 0: | |
212 | self._indent -= 1 | |
213 | ||
214 | ||
215 | def SetWxLog(self, value): | |
216 | self._wxLog = value | |
217 | ||
218 | ||
219 | def SetStream(self, value): | |
220 | if value: | |
221 | self._outstream_stack.append( self._outstream ) | |
222 | self._outstream = value | |
223 | elif value is None and len(self._outstream_stack) > 0: | |
224 | self._outstream = self._outstream_stack.pop(-1) | |
225 | ||
226 | ||
227 | #------------------------------------------------------------ | |
228 | ||
229 | if __name__ == "__main__": | |
d4b73b1b RD |
230 | import sys |
231 | import wx | |
232 | ||
233 | wx.Log_SetActiveTarget( wx.LogStderr() ) | |
d14a1e28 RD |
234 | logger = Logger('module') |
235 | dbg = logger.dbg | |
236 | dbg(enable=1) | |
237 | logger('test __call__ interface') | |
238 | dbg('testing wxLog output to stderr:', wxlog=1, indent=1) | |
239 | dbg('1,2,3...') | |
d4b73b1b RD |
240 | dbg('testing wx.LogNull:') |
241 | devnull = wx.LogNull() | |
d14a1e28 RD |
242 | dbg('4,5,6...') # shouldn't print, according to doc... |
243 | del devnull | |
d4b73b1b RD |
244 | dbg('(resuming to wx.LogStdErr)', '7,8,9...', indent=0) |
245 | dbg('disabling wx.Log output, switching to stderr:') | |
d14a1e28 RD |
246 | dbg(wxlog=0, stream=sys.stderr) |
247 | dbg(logger._outstream, 'switching back to stdout:') | |
248 | dbg(stream=None) | |
249 | dbg(logger._outstream ) | |
250 | def foo(str): | |
251 | dbg('foo:', indent=1) | |
252 | dbg(str, indent=0) | |
253 | foo('testing dbg inside function') | |
254 | class bar(Logger): | |
255 | def __init__(self, name): | |
256 | Logger.__init__(self, name) | |
257 | def enable(self, value): | |
258 | self.dbg(enable=value) | |
259 | def foo(self, str): | |
260 | self.dbg('foo:', indent=1) | |
261 | self.dbg(str, indent=0) | |
262 | f = bar('class mixin') | |
263 | f.foo("shouldn't print") | |
264 | f.enable(1) | |
265 | f.foo("should print") | |
266 | dbg('test completed.', enable=0) | |
267 | dbg('(double-checking ;-)') | |
1fded56b | 268 |