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 14try: 15from subprocess import CalledProcessError 16exceptImportError: 17# from python2.7:subprocess.py 18# Exception classes used by this module. 19classCalledProcessError(Exception): 20"""This exception is raised when a process run by check_call() returns 21 a non-zero exit status. The exit status will be stored in the 22 returncode attribute.""" 23def__init__(self, returncode, cmd): 24 self.returncode = returncode 25 self.cmd = cmd 26def__str__(self): 27return"Command '%s' returned non-zero exit status%d"% (self.cmd, self.returncode) 28 29 30# Whether or not to show debug messages 31DEBUG =False 32 33defnotify(msg, *args): 34"""Print a message to stderr.""" 35print>> sys.stderr, msg % args 36 37defdebug(msg, *args): 38"""Print a debug message to stderr when DEBUG is enabled.""" 39if DEBUG: 40print>> sys.stderr, msg % args 41 42deferror(msg, *args): 43"""Print an error message to stderr.""" 44print>> sys.stderr,"ERROR:", msg % args 45 46defwarn(msg, *args): 47"""Print a warning message to stderr.""" 48print>> sys.stderr,"warning:", msg % args 49 50defdie(msg, *args): 51"""Print as error message to stderr and exit the program.""" 52error(msg, *args) 53 sys.exit(1) 54 55 56classProgressIndicator(object): 57 58"""Simple progress indicator. 59 60 Displayed as a spinning character by default, but can be customized 61 by passing custom messages that overrides the spinning character. 62 63 """ 64 65 States = ("|","/","-","\\") 66 67def__init__(self, prefix ="", f = sys.stdout): 68"""Create a new ProgressIndicator, bound to the given file object.""" 69 self.n =0# Simple progress counter 70 self.f = f # Progress is written to this file object 71 self.prev_len =0# Length of previous msg (to be overwritten) 72 self.prefix = prefix # Prefix prepended to each progress message 73 self.prefix_lens = []# Stack of prefix string lengths 74 75defpushprefix(self, prefix): 76"""Append the given prefix onto the prefix stack.""" 77 self.prefix_lens.append(len(self.prefix)) 78 self.prefix += prefix 79 80defpopprefix(self): 81"""Remove the last prefix from the prefix stack.""" 82 prev_len = self.prefix_lens.pop() 83 self.prefix = self.prefix[:prev_len] 84 85def__call__(self, msg =None, lf =False): 86"""Indicate progress, possibly with a custom message.""" 87if msg is None: 88 msg = self.States[self.n %len(self.States)] 89 msg = self.prefix + msg 90print>> self.f,"\r%-*s"% (self.prev_len, msg), 91 self.prev_len =len(msg.expandtabs()) 92if lf: 93print>> self.f 94 self.prev_len =0 95 self.n +=1 96 97deffinish(self, msg ="done", noprefix =False): 98"""Finalize progress indication with the given message.""" 99if noprefix: 100 self.prefix ="" 101self(msg,True) 102 103 104defstart_command(args, cwd =None, shell =False, add_env =None, 105 stdin = subprocess.PIPE, stdout = subprocess.PIPE, 106 stderr = subprocess.PIPE): 107"""Start the given command, and return a subprocess object. 108 109 This provides a simpler interface to the subprocess module. 110 111 """ 112 env =None 113if add_env is not None: 114 env = os.environ.copy() 115 env.update(add_env) 116return subprocess.Popen(args, bufsize =1, stdin = stdin, stdout = stdout, 117 stderr = stderr, cwd = cwd, shell = shell, 118 env = env, universal_newlines =True) 119 120 121defrun_command(args, cwd =None, shell =False, add_env =None, 122 flag_error =True): 123"""Run the given command to completion, and return its results. 124 125 This provides a simpler interface to the subprocess module. 126 127 The results are formatted as a 3-tuple: (exit_code, output, errors) 128 129 If flag_error is enabled, Error messages will be produced if the 130 subprocess terminated with a non-zero exit code and/or stderr 131 output. 132 133 The other arguments are passed on to start_command(). 134 135 """ 136 process =start_command(args, cwd, shell, add_env) 137(output, errors) = process.communicate() 138 exit_code = process.returncode 139if flag_error and errors: 140error("'%s' returned errors:\n---\n%s---"," ".join(args), errors) 141if flag_error and exit_code: 142error("'%s' returned exit code%i"," ".join(args), exit_code) 143return(exit_code, output, errors) 144 145 146# from python2.7:subprocess.py 147defcall(*popenargs, **kwargs): 148"""Run command with arguments. Wait for command to complete, then 149 return the returncode attribute. 150 151 The arguments are the same as for the Popen constructor. Example: 152 153 retcode = call(["ls", "-l"]) 154 """ 155return subprocess.Popen(*popenargs, **kwargs).wait() 156 157 158# from python2.7:subprocess.py 159defcheck_call(*popenargs, **kwargs): 160"""Run command with arguments. Wait for command to complete. If 161 the exit code was zero then return, otherwise raise 162 CalledProcessError. The CalledProcessError object will have the 163 return code in the returncode attribute. 164 165 The arguments are the same as for the Popen constructor. Example: 166 167 check_call(["ls", "-l"]) 168 """ 169 retcode =call(*popenargs, **kwargs) 170if retcode: 171 cmd = kwargs.get("args") 172if cmd is None: 173 cmd = popenargs[0] 174raiseCalledProcessError(retcode, cmd) 175return0 176 177 178# from python2.7:subprocess.py 179defcheck_output(*popenargs, **kwargs): 180 r"""Run command with arguments and return its output as a byte string. 181 182 If the exit code was non-zero it raises a CalledProcessError. The 183 CalledProcessError object will have the return code in the returncode 184 attribute and output in the output attribute. 185 186 The arguments are the same as for the Popen constructor. Example: 187 188 >>> check_output(["ls", "-l", "/dev/null"]) 189 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' 190 191 The stdout argument is not allowed as it is used internally. 192 To capture standard error in the result, use stderr=STDOUT. 193 194 >>> check_output(["/bin/sh", "-c", 195 ... "ls -l non_existent_file ; exit 0"], 196 ... stderr=STDOUT) 197 'ls: non_existent_file: No such file or directory\n' 198 """ 199if'stdout'in kwargs: 200raiseValueError('stdout argument not allowed, it will be overridden.') 201 process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) 202 output, unused_err = process.communicate() 203 retcode = process.poll() 204if retcode: 205 cmd = kwargs.get("args") 206if cmd is None: 207 cmd = popenargs[0] 208raise subprocess.CalledProcessError(retcode, cmd) 209return output 210 211 212deffile_reader_method(missing_ok =False): 213"""Decorator for simplifying reading of files. 214 215 If missing_ok is True, a failure to open a file for reading will 216 not raise the usual IOError, but instead the wrapped method will be 217 called with f == None. The method must in this case properly 218 handle f == None. 219 220 """ 221def_wrap(method): 222"""Teach given method to handle both filenames and file objects. 223 224 The given method must take a file object as its second argument 225 (the first argument being 'self', of course). This decorator 226 will take a filename given as the second argument and promote 227 it to a file object. 228 229 """ 230def_wrapped_method(self, filename, *args, **kwargs): 231ifisinstance(filename,file): 232 f = filename 233else: 234try: 235 f =open(filename,'r') 236exceptIOError: 237if missing_ok: 238 f =None 239else: 240raise 241try: 242returnmethod(self, f, *args, **kwargs) 243finally: 244if notisinstance(filename,file)and f: 245 f.close() 246return _wrapped_method 247return _wrap 248 249 250deffile_writer_method(method): 251"""Decorator for simplifying writing of files. 252 253 Enables the given method to handle both filenames and file objects. 254 255 The given method must take a file object as its second argument 256 (the first argument being 'self', of course). This decorator will 257 take a filename given as the second argument and promote it to a 258 file object. 259 260 """ 261def_new_method(self, filename, *args, **kwargs): 262ifisinstance(filename,file): 263 f = filename 264else: 265# Make sure the containing directory exists 266 parent_dir = os.path.dirname(filename) 267if not os.path.isdir(parent_dir): 268 os.makedirs(parent_dir) 269 f =open(filename,'w') 270try: 271returnmethod(self, f, *args, **kwargs) 272finally: 273if notisinstance(filename,file): 274 f.close() 275return _new_method