#!/usr/bin/python """\ %(prog)s.py: Python module for error handling (console programs) """ __author__ = "Tobias Herp " # TODO: # * introspection: # - skip foreign functions and objects (from imported modules) # - for pairs of identical functions (e.g. err and error), # print once and refer to it # - filtering by name # - put the whole introspection functionality in a special module from sys import stdout, stderr, exit, argv, exc_info from os.path import basename _examples = """\ Simple usage example: The following Python script expects integer numbers as commandline arguments. It first evaluates the whole commandline and yields a message for *every* ill argument; finally it checks whether there were any errors and otherwise prints the product of all numbers: from %(prog)s import err, check_errors from sys import argv numbers = [] for a in argv[1:]: try: numbers.append(int(a)) except ValueError: err('%%r is not a valid integer' %% a) check_errors() print reduce(lambda a, b: a*b, numbers) (To try it in an interactive shell, you can replace line 2 by something like "argv = 'ignored 1 a 2 b 3 4'.split()". If argv[1:] contains non-integers, check_errors() will terminate the Python shell.) Help, incl. how to get lists of functions and data: %(prog)s.py -h """ VERSION = '.'.join(map(str, (0, 2, # cleaned up; global imports; OptionGroup 1, # shebang/vim com.ts; sorting; TODOs; __author__; _p_sep 2, # minor language specific corrections ))) RC_OK = 0 # trivial but documented RC_ERROR = 1 # returned to the shell unless a counter is used RC_HELP = 3 # returned when help was displayed RC_CANCEL = 98 # user exit, e.g. after prompt RC_ABORT = 99 # user exit via keyboard interrupt def set_progname(name=None): """ set the name used by warn, err, fatal and info; should not be necessary in programs: set_progname(progname()) """ global _PROGNAME, _LOGNAME if name: _PROGNAME = name+':' _LOGNAME = '['+name+']' else: _PROGNAME = '' _LOGNAME = '' set_progname('errors') def info(text, to=None): """ print the given info, by default to stderr """ if to is None: to = stdout print >> to, _PROGNAME+'i', text WARNINGS = 0 def warn(text): """ print the given warning and increase the warnings counter """ global WARNINGS WARNINGS += 1 print >> stderr, _PROGNAME+'W', text warning = warn ERRORS = 0 def err(text, code=None): """ print the given error message and, if given a code <> None, terminate the program; otherwise increase the error counter """ global ERRORS print >> stderr, _PROGNAME+'E', text if code is not None: exit(code or 1) ERRORS += 1 error = err def fatal(text=None, code=None, tell=True, count=None, help=None): """ print a text and terminate the program. Options: text -- the text to print to stderr Optional: code -- the code to return to the shell; default: the number ERRORS of the errors occured so far, or 1 tell -- 1 or True: print an information about the number of errors 2: like 1, but additionally mention the number of warnings None: leave the decision to the function count -- boolean: count the current error? (False by default) help -- a hint about the --help commandline argument """ global ERRORS if count is None: count = False # bool(text) if count: ERRORS += 1 if tell is None: # Automatik tell = not text or not count if text is not None: print >> stderr, _PROGNAME+'!', text RC = code or ERRORS or RC_ERROR or 1 if tell: if ERRORS: info('%d error(s)' % ERRORS, stderr) if WARNINGS and tell > 1: info('%d warning(s)' % WARNINGS, stderr) if help: info(help) if tell: info('Exiting with RC %d' % RC, stderr) exit(RC) def errline(text=''): """ print a line of (by default empty) text to stderr text -- the text """ print >> stderr, text def progname(stripchar=None, stripext=True): """ return the name of the program (like basename, but with the '.py' extension stripped as well) """ tmp = basename(argv[0]) if stripext and tmp.endswith('.py'): tmp = tmp[:-3] if stripchar: tmp = tmp.rstrip(stripchar) return tmp def check_errors(text=None, code=None): """ If ERRORS were counted, terminate the program. Hand over some arguments to the fatal() function: text -- the 'help' argument for fatal() (suppress by specifying '') """ if text is None: text = "Specify -h or --help for help" if ERRORS: fatal(code=code, tell=True, count=False, help=text) def prompt(txt): """ display a prompt and ask for a yes/no decision. Rudimentary version; possible answers still hardcoded (yes/ja or no/nein), and no way to get something like yes(no/all/none/abort/quit """ idx = 0 txt += ' (y/n) ' txt2 = 'Please answer y(es) or n(o)! ' try: while True: try: answer = raw_input(idx and txt2 or txt) except AssertionError, e: # der isses nicht... print >> stderr, idx and txt2 or txt, answer = raw_input(': ') answer = answer.strip().lower() if not answer: idx = 1 continue if 'yes'.startswith(answer): return 1 elif 'ja'.startswith(answer): return 1 elif 'no'.startswith(answer): return 0 elif 'nein'.startswith(answer): return 0 idx = (idx + 1) % 5 finally: print set_progname(progname()) try: from gettext import gettext as _ except ImportError: def _(message): return message # not executed when imported: if __name__ == '__main__': if ERRORS or WARNINGS: print _keys = globals().keys() try: from enhopa import OptionParser, OptionGroup except ImportError: from optparse import OptionParser, OptionGroup _prog = progname() parser = OptionParser(usage="import %s | %%prog --help" % _prog, version=VERSION) parser.set_description(__doc__ % {'prog': _prog}) _group = OptionGroup(parser, 'Module information') _group.add_option('--functions', '-f', action='store_true', help='tell about the functions') # no classes yet: if 0:\ _group.add_option('--classes', '-c', action='store_true', help='tell about the classes') _group.add_option('--data', '-d', action='store_true', help='tell about the data') _group.add_option('--all', '-a', action='store_true', help='shortcut for -fd') # no classes yet _group.add_option('--usage', '-u', action='store_true', help='print a short usage example') parser.add_option_group(_group) if hasattr(parser,'set_collecting_group'): parser.set_collecting_group() (option, args) = parser.parse_args() # not to be imported by programs, and thus not listed: from os import linesep as _ls _p_sep = _ls * 2 # paragraph separator if option.usage: print _p_sep.join((__doc__.strip(), _examples.strip()) ) % {'prog': _prog} print from inspect import isclass, isfunction, getargspec, formatargspec _classes = [] _functions = [] _data = [] for k in _keys: o = globals()[k] if k.startswith('_'): pass elif isclass(o): _classes.append((k, o)) elif isfunction(o): _functions.append((k, o)) else: _data.append((k, o)) if option.all: option.functions = True option.classes = True option.data = True if not (option.functions # or option.classes or option.data or option.usage): print _p_sep.join((__doc__.strip(), 'Help: %(prog)s -h', )) % {'prog': _prog} exit(-1) def headline(txt): print txt+':' print '~'*len(txt) if option.data: headline('Data') _data.sort() for k, o in _data: print ' %-13s\t%r' % (k+':', o) print;print if option.functions: headline('Functions') _functions.sort() for k, o in _functions: print ' %s%s:' % (k, formatargspec(*getargspec(o))) try: print ' %s' % o.__doc__.strip() except AttributeError: print ' (sorry, kein Docstring)' print print if 0 and option.classes: headline('Classes') for k, o in _classes: if not issubclass(o, Exception): print ' %s' % k try: print ' '+o.__doc__.strip() print except ValueError: pass print headline('Exception classes') for k, o in _classes: if issubclass(o, Exception): print ' %s' % k print # vim: tabstop=8 softtabstop=4 expandtab smartindent shiftwidth=4 textwidth=79