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