1#!/usr/bin/env python 2 3"""Misc. useful functionality used by the rest of this package. 4 5This module provides common functionality used by the other modules in 6this package. 7 8""" 9 10import sys 11import os 12import subprocess 13 14 15# Whether or not to show debug messages 16DEBUG =False 17 18defnotify(msg, *args): 19"""Print a message to stderr.""" 20print>> sys.stderr, msg % args 21 22defdebug(msg, *args): 23"""Print a debug message to stderr when DEBUG is enabled.""" 24if DEBUG: 25print>> sys.stderr, msg % args 26 27deferror(msg, *args): 28"""Print an error message to stderr.""" 29print>> sys.stderr,"ERROR:", msg % args 30 31defwarn(msg, *args): 32"""Print a warning message to stderr.""" 33print>> sys.stderr,"warning:", msg % args 34 35defdie(msg, *args): 36"""Print as error message to stderr and exit the program.""" 37error(msg, *args) 38 sys.exit(1) 39 40 41classProgressIndicator(object): 42 43"""Simple progress indicator. 44 45 Displayed as a spinning character by default, but can be customized 46 by passing custom messages that overrides the spinning character. 47 48 """ 49 50 States = ("|","/","-","\\") 51 52def__init__(self, prefix ="", f = sys.stdout): 53"""Create a new ProgressIndicator, bound to the given file object.""" 54 self.n =0# Simple progress counter 55 self.f = f # Progress is written to this file object 56 self.prev_len =0# Length of previous msg (to be overwritten) 57 self.prefix = prefix # Prefix prepended to each progress message 58 self.prefix_lens = []# Stack of prefix string lengths 59 60defpushprefix(self, prefix): 61"""Append the given prefix onto the prefix stack.""" 62 self.prefix_lens.append(len(self.prefix)) 63 self.prefix += prefix 64 65defpopprefix(self): 66"""Remove the last prefix from the prefix stack.""" 67 prev_len = self.prefix_lens.pop() 68 self.prefix = self.prefix[:prev_len] 69 70def__call__(self, msg =None, lf =False): 71"""Indicate progress, possibly with a custom message.""" 72if msg is None: 73 msg = self.States[self.n %len(self.States)] 74 msg = self.prefix + msg 75print>> self.f,"\r%-*s"% (self.prev_len, msg), 76 self.prev_len =len(msg.expandtabs()) 77if lf: 78print>> self.f 79 self.prev_len =0 80 self.n +=1 81 82deffinish(self, msg ="done", noprefix =False): 83"""Finalize progress indication with the given message.""" 84if noprefix: 85 self.prefix ="" 86self(msg,True) 87 88 89defstart_command(args, cwd =None, shell =False, add_env =None, 90 stdin = subprocess.PIPE, stdout = subprocess.PIPE, 91 stderr = subprocess.PIPE): 92"""Start the given command, and return a subprocess object. 93 94 This provides a simpler interface to the subprocess module. 95 96 """ 97 env =None 98if add_env is not None: 99 env = os.environ.copy() 100 env.update(add_env) 101return subprocess.Popen(args, bufsize =1, stdin = stdin, stdout = stdout, 102 stderr = stderr, cwd = cwd, shell = shell, 103 env = env, universal_newlines =True) 104 105 106defrun_command(args, cwd =None, shell =False, add_env =None, 107 flag_error =True): 108"""Run the given command to completion, and return its results. 109 110 This provides a simpler interface to the subprocess module. 111 112 The results are formatted as a 3-tuple: (exit_code, output, errors) 113 114 If flag_error is enabled, Error messages will be produced if the 115 subprocess terminated with a non-zero exit code and/or stderr 116 output. 117 118 The other arguments are passed on to start_command(). 119 120 """ 121 process =start_command(args, cwd, shell, add_env) 122(output, errors) = process.communicate() 123 exit_code = process.returncode 124if flag_error and errors: 125error("'%s' returned errors:\n---\n%s---"," ".join(args), errors) 126if flag_error and exit_code: 127error("'%s' returned exit code%i"," ".join(args), exit_code) 128return(exit_code, output, errors) 129 130 131deffile_reader_method(missing_ok =False): 132"""Decorator for simplifying reading of files. 133 134 If missing_ok is True, a failure to open a file for reading will 135 not raise the usual IOError, but instead the wrapped method will be 136 called with f == None. The method must in this case properly 137 handle f == None. 138 139 """ 140def_wrap(method): 141"""Teach given method to handle both filenames and file objects. 142 143 The given method must take a file object as its second argument 144 (the first argument being 'self', of course). This decorator 145 will take a filename given as the second argument and promote 146 it to a file object. 147 148 """ 149def_wrapped_method(self, filename, *args, **kwargs): 150ifisinstance(filename,file): 151 f = filename 152else: 153try: 154 f =open(filename,'r') 155exceptIOError: 156if missing_ok: 157 f =None 158else: 159raise 160try: 161returnmethod(self, f, *args, **kwargs) 162finally: 163if notisinstance(filename,file)and f: 164 f.close() 165return _wrapped_method 166return _wrap 167 168 169deffile_writer_method(method): 170"""Decorator for simplifying writing of files. 171 172 Enables the given method to handle both filenames and file objects. 173 174 The given method must take a file object as its second argument 175 (the first argument being 'self', of course). This decorator will 176 take a filename given as the second argument and promote it to a 177 file object. 178 179 """ 180def_new_method(self, filename, *args, **kwargs): 181ifisinstance(filename,file): 182 f = filename 183else: 184# Make sure the containing directory exists 185 parent_dir = os.path.dirname(filename) 186if not os.path.isdir(parent_dir): 187 os.makedirs(parent_dir) 188 f =open(filename,'w') 189try: 190returnmethod(self, f, *args, **kwargs) 191finally: 192if notisinstance(filename,file): 193 f.close() 194return _new_method