initial commit
[ebuildfind.git] / commands / lib / layman / debug.py
blobc5cf3a0aafae686e7a3b7a49df7a2be19f48a882
1 #################################################################################
2 # LAYMAN - DEBUGGING FUNCTIONS
3 #################################################################################
4 # debug.py -- Utility function for debugging
5 # Copyright 2005 - 2008 Gunnar Wrobel
6 # Distributed under the terms of the GNU General Public License v2
8 __version__ = "$Id: debug.py 153 2006-06-05 06:03:16Z wrobel $"
10 #################################################################################
12 ## Dependancies
14 #################################################################################
16 import sys, inspect, types
18 from optparse import OptionGroup
20 #################################################################################
22 ## Color codes (taken from portage)
24 #################################################################################
26 esc_seq = '\x1b['
28 codes = {}
29 codes['reset'] = esc_seq + '39;49;00m'
30 codes['red'] = esc_seq + '31;01m'
31 codes['green'] = esc_seq + '32;01m'
32 codes['yellow'] = esc_seq + '33;01m'
33 codes['turquoise'] = esc_seq + '36;01m'
35 #################################################################################
37 ## Message Class
39 #################################################################################
41 class Message:
42 #FIXME: Think about some simple doctests before you modify this class the
43 # next time.
45 def __init__(self, module = '',
46 err = sys.stderr,
47 dbg = sys.stderr,
48 debugging_level = 4,
49 debugging_verbosity = 2,
50 info_level = 4,
51 warn_level = 4,
52 col = True,
53 mth = ['*'],
54 obj = ['*'],
55 var = ['*']):
57 # A description of the module that is being debugged
58 self.debug_env = module
60 # Where should the debugging output go? This can also be a file
61 self.debug_out = dbg
63 # Where should the error output go? This can also be a file
64 self.error_out = err
66 # The higher the level the more information you will get
67 self.warn_lev = warn_level
69 # The higher the level the more information you will get
70 self.info_lev = info_level
72 # The highest level of debugging messages acceptable for output
73 # The higher the level the more output you will get
74 self.debug_lev = debugging_level
76 # The debugging output can range from very verbose (3) to
77 # very compressed (1)
78 self.debug_vrb = debugging_verbosity
80 # Which methods should actually be debugged?
81 # Use '*' to indicate 'All methods'
82 self.debug_mth = mth
84 # Which objects should actually be debugged?
85 # Use '*' to indicate 'All objects'
86 self.debug_obj = obj
88 # Which variables should actually be debugged?
89 # Use '*' to indicate 'All variables'
90 self.debug_var = var
92 # Exclude class variables by default
93 self.show_class_variables = False
95 # Should the output be colored?
96 self.use_color = col
98 self.has_error = False
101 ############################################################################
102 # Add command line options
104 def cli_opts(self, parser):
106 group = OptionGroup(parser,
107 '<Debugging options>',
108 'Control the debugging features of '
109 + self.debug_env)
111 group.add_option('--debug',
112 action = 'store_true',
113 help = 'Activates debugging features.')
115 group.add_option('--debug-level',
116 action = 'store',
117 type = 'int',
118 help = 'A value between 0 and 10. 0 means no debugging '
119 'messages will be selected, 10 selects all debugging me'
120 'ssages. Default is "4".')
122 group.add_option('--debug-verbose',
123 action = 'store',
124 type = 'int',
125 help = 'A value between 1 and 3. Lower values yield les'
126 's verbose debugging output. Default is "2".')
128 group.add_option('--debug-methods',
129 action = 'store',
130 help = 'Limits the methods that will return debugging o'
131 'utput. The function name is sufficient and there is no'
132 'difference between class methods or general functions.'
133 ' Several methods can be specified by seperating them w'
134 ' with a comma. Default is "*" which specifies all meth'
135 'ods.')
137 group.add_option('--debug-classes',
138 action = 'store',
139 help = 'Limits the classes that will return debugging o'
140 'utput. Specify only the class name not including the m'
141 'odules in which the class is defined (e.g. MyModule.ma'
142 'in.Main should only be represented by "Main"). Several'
143 'classes can be specified by seperating them with a com'
144 'ma. Default is "*" which specifies all classes.')
146 group.add_option('--debug-variables',
147 action = 'store',
148 help = 'Limits the variables that will return debugging'
149 ' output. Several variables can be specified by seperat'
150 'ing them with a comma. Default is "*" which specifies '
151 'all variables.')
153 group.add_option('--debug-class-vars',
154 action = 'store_true',
155 help = 'In default mode the debugging code will only re'
156 'turn information on the local variable which does not '
157 'include the class variables. Use this switch to add al'
158 'l values that are provided by "self".')
160 group.add_option('--debug-nocolor',
161 action = 'store_true',
162 help = 'Deactivates colors in the debugging output.')
164 parser.add_option_group(group)
167 #############################################################################
168 # Handle command line options
170 def cli_handle(self, options):
172 if (options.__dict__.has_key('debug')
173 and options.__dict__['debug']):
174 self.debug_on()
175 else:
176 self.debug_off()
177 return
179 if (options.__dict__.has_key('debug_class_vars')
180 and options.__dict__['debug_class_vars']):
181 self.class_variables_on()
182 else:
183 self.class_variables_off()
185 if (options.__dict__.has_key('debug_nocolor')
186 and options.__dict__['debug_nocolor']):
187 self.color_off()
188 else:
189 self.color_on()
191 if (options.__dict__.has_key('debug_level') and
192 options.__dict__['debug_level']):
193 dbglvl = int(options.__dict__['debug_level'])
194 if dbglvl < 0:
195 dbglvl = 0
196 if dbglvl > 10:
197 dbglvl = 10
198 self.set_debug_level(dbglvl)
200 if (options.__dict__.has_key('debug_verbose') and
201 options.__dict__['debug_verbose']):
202 dbgvrb = int(options.__dict__['debug_verbose'])
203 if dbgvrb < 1:
204 dbgvrb = 1
205 if dbgvrb > 3:
206 dbgvrb = 3
207 self.set_debug_verbosity(dbgvrb)
209 for i in [('debug_methods', self.set_debug_methods),
210 ('debug_classes', self.set_debug_classes),
211 ('debug_variables', self.set_debug_variables),]:
213 if (options.__dict__.has_key(i[0]) and
214 options.__dict__[i[0]]):
215 i[1](options.__dict__[i[0]])
218 #############################################################################
219 ## Helper Functions
221 def set_module(self, module):
223 self.debug_env = module
225 def set_debug_methods(self, methods):
227 methods = methods.split(',')
229 if methods:
230 self.debug_mth = methods
232 def set_debug_classes(self, classes):
234 classes = classes.split(',')
236 if classes:
237 self.debug_obj = classes
239 def set_debug_variables(self, variables):
241 variables = variables.split(',')
243 if variables:
244 self.debug_var = variables
246 def maybe_color (self, col, text):
247 if self.use_color:
248 return codes[col] + text + codes['reset']
249 return text
251 def set_info_level(self, info_level = 4):
252 self.info_lev = info_level
254 def info_off(self):
255 self.set_info_level(0)
257 def info_on(self, info_level = 4):
258 self.set_info_level(info_level)
260 def set_warn_level(self, warn_level = 4):
261 self.warn_lev = warn_level
263 def warn_off(self):
264 self.set_warn_level(0)
266 def warn_on(self, warn_level = 4):
267 self.set_warn_level(warn_level)
269 def set_debug_level(self, debugging_level = 4):
270 self.debug_lev = debugging_level
272 def set_debug_verbosity(self, debugging_verbosity = 2):
273 self.debug_vrb = debugging_verbosity
275 def debug_off(self):
276 self.set_debug_level(0)
278 def debug_on(self):
279 self.set_debug_level()
281 def color_off(self):
282 self.use_color = False
284 def color_on(self):
285 self.use_color = True
287 def class_variables_off(self):
288 self.show_class_variables = False
290 def class_variables_on(self):
291 self.show_class_variables = True
293 #############################################################################
294 ## Output Functions
296 def notice (self, note):
297 print note
299 def info (self, info, level = 4):
301 if type(info) not in types.StringTypes:
302 info = str(info)
304 if level > self.info_lev:
305 return
307 for i in info.split('\n'):
308 print self.maybe_color('green', '* ') + i
310 def status (self, message, status, info = 'ignored'):
312 if type(message) not in types.StringTypes:
313 message = str(message)
315 lines = message.split('\n')
317 if not lines:
318 return
320 for i in lines[0:-1]:
321 print self.maybe_color('green', '* ') + i
323 i = lines[-1]
325 if len(i) > 58:
326 i = i[0:57]
328 if status == 1:
329 result = '[' + self.maybe_color('green', 'ok') + ']'
330 elif status == 0:
331 result = '[' + self.maybe_color('red', 'failed') + ']'
332 else:
333 result = '[' + self.maybe_color('yellow', info) + ']'
335 print self.maybe_color('green', '* ') + i + ' ' + '.' * (58 - len(i)) \
336 + ' ' + result
338 def warn (self, warn, level = 4):
340 if type(warn) not in types.StringTypes:
341 warn = str(warn)
343 if level > self.warn_lev:
344 return
346 for i in warn.split('\n'):
347 print self.maybe_color('yellow', '* ') + i
349 def error (self, error):
351 if type(error) not in types.StringTypes:
352 error = str(error)
354 for i in error.split('\n'):
355 print >> self.error_out, self.maybe_color('red', '* ') + i
356 self.has_error = True
358 def die (self, error):
360 if type(error) not in types.StringTypes:
361 error = str(error)
363 for i in error.split('\n'):
364 self.error(self.maybe_color('red', 'Fatal error: ') + i)
365 self.error(self.maybe_color('red', 'Fatal error(s) - aborting'))
366 sys.exit(1)
368 def debug (self, message, level = 4):
370 This is a generic debugging method.
372 ## Check the debug level first. This is the most inexpensive check.
373 if level > self.debug_lev:
374 return
376 ## Maybe this should be debugged. So get the stack first.
377 stack = inspect.stack()
379 ## This can probably never happen but does not harm to check
380 ## that there is actually something calling this function
381 if len(stack) < 2:
382 return
384 ## Get the stack length to determine indentation of the debugging output
385 stacklength = len(stack)
386 ls = ' ' * stacklength
388 ## Get the information about the caller
389 caller = stack[1]
391 ## The function name of the calling frame is the fourth item in the list
392 callermethod = caller[3]
394 ## Is this actually one of the methods that should be debugged?
395 if not '*' in self.debug_mth and not callermethod in self.debug_mth:
396 return
398 ## Still looks like this should be debugged. So retrieve the dictionary
399 ## of local variables from the caller
400 callerlocals = inspect.getargvalues(caller[0])[3]
402 ## Is the caller an obejct? If so he provides 'self'
403 if 'self' in callerlocals.keys():
404 callerobject = callerlocals['self']
405 del callerlocals['self']
406 if self.show_class_variables:
407 cv = inspect.getmembers(callerobject,
408 lambda x: not inspect.ismethod(x))
409 callerlocals.sync(cv)
410 else:
411 callerobject = None
413 # Remove variables not requested
414 if not '*' in self.debug_var:
415 callerlocals = dict([i for i in callerlocals.items()
416 if i[0] in self.debug_var])
418 ## Is the object among the list of objects to debug?
419 if (not '*' in self.debug_obj and
420 not str(callerobject.__class__.__name__) in self.debug_obj):
421 return
423 if type(message) not in types.StringTypes:
424 message = str(message)
426 def breaklines(x):
428 Helper function to keep width of the debugging output.
430 This may look ugly for arrays but it is acceptable and not
431 breaking the line would break the output format
433 ## Get the number of lines we need (rounded down)
434 lines = len(x) // 60
435 if lines > 0:
436 for j in range(lines):
437 ## Print line with continuation marker
438 print >> self.debug_out, ls + '// ' + x[0:60] + ' \\'
439 ## Remove printed characters from output
440 x = x[60:]
441 ## Print final line
442 print >> self.debug_out, ls + '// ' + x
444 if self.debug_vrb == 1:
445 # Top line indicates class and method
446 c = ''
447 if callerobject:
448 c += 'Class: ' + str(callerobject.__class__.__name__) + ' | '
449 if callermethod:
450 c += 'Method: ' + str(callermethod)
451 print >> self.debug_out, '// ' + c
452 # Selected variables follow
453 if callerlocals:
454 for i,j in callerlocals.items():
455 print >> self.debug_out, '// ' \
456 + self.maybe_color('turquoise', str(i)) + ':' + str(j)
457 # Finally the message
458 print >> self.debug_out, self.maybe_color('yellow', message)
459 return
461 if self.debug_vrb == 3:
462 print >> self.debug_out, ls + '/////////////////////////////////' + \
463 '////////////////////////////////'
465 # General information about what is being debugged
466 #(module name or similar)
467 print >> self.debug_out, ls + '// ' + self.debug_env
468 print >> self.debug_out, ls + '//-----------------------------------' + \
469 '----------------------------'
471 ## If the caller is a class print the name here
472 if callerobject:
473 print >> self.debug_out, ls + \
474 '// Object Class: ' + str(callerobject.__class__.__name__)
476 ## If the method has been extracted print it here
477 if callermethod:
478 print >> self.debug_out, ls + '// ' \
479 + self.maybe_color('green', 'Method: ') + str(callermethod)
480 if self.debug_vrb == 3:
481 print >> self.debug_out, ls + '//---------------------------' + \
482 '------------------------------------'
484 ## Print the information on all available local variables
485 if callerlocals:
486 if self.debug_vrb == 3:
487 print >> self.debug_out, ls + '//'
488 print >> self.debug_out, ls + '// VALUES '
489 for i,j in callerlocals.items():
490 print >> self.debug_out, ls + '// ------------------> ' \
491 + self.maybe_color('turquoise', str(i)) + ':'
492 breaklines(str(j))
493 if self.debug_vrb == 3:
494 print >> self.debug_out, ls + '//------------------------------'\
495 '---------------------------------'
497 # Finally print the message
498 breaklines(self.maybe_color('yellow', message))
500 if self.debug_vrb == 3:
501 print >> self.debug_out, ls + '//-------------------------------' + \
502 '--------------------------------'
503 print >> self.debug_out, ls + '/////////////////////////////////' + \
504 '////////////////////////////////'
506 ## gloabal message handler
507 OUT = Message('layman')