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 178deffile_reader_method(missing_ok =False): 179"""Decorator for simplifying reading of files. 180 181 If missing_ok is True, a failure to open a file for reading will 182 not raise the usual IOError, but instead the wrapped method will be 183 called with f == None. The method must in this case properly 184 handle f == None. 185 186 """ 187def_wrap(method): 188"""Teach given method to handle both filenames and file objects. 189 190 The given method must take a file object as its second argument 191 (the first argument being 'self', of course). This decorator 192 will take a filename given as the second argument and promote 193 it to a file object. 194 195 """ 196def_wrapped_method(self, filename, *args, **kwargs): 197ifisinstance(filename,file): 198 f = filename 199else: 200try: 201 f =open(filename,'r') 202exceptIOError: 203if missing_ok: 204 f =None 205else: 206raise 207try: 208returnmethod(self, f, *args, **kwargs) 209finally: 210if notisinstance(filename,file)and f: 211 f.close() 212return _wrapped_method 213return _wrap 214 215 216deffile_writer_method(method): 217"""Decorator for simplifying writing of files. 218 219 Enables the given method to handle both filenames and file objects. 220 221 The given method must take a file object as its second argument 222 (the first argument being 'self', of course). This decorator will 223 take a filename given as the second argument and promote it to a 224 file object. 225 226 """ 227def_new_method(self, filename, *args, **kwargs): 228ifisinstance(filename,file): 229 f = filename 230else: 231# Make sure the containing directory exists 232 parent_dir = os.path.dirname(filename) 233if not os.path.isdir(parent_dir): 234 os.makedirs(parent_dir) 235 f =open(filename,'w') 236try: 237returnmethod(self, f, *args, **kwargs) 238finally: 239if notisinstance(filename,file): 240 f.close() 241return _new_method