| #!/usr/bin/env python |
| |
| """Misc. useful functionality used by the rest of this package. |
| |
| This module provides common functionality used by the other modules in |
| this package. |
| |
| """ |
| |
| import sys |
| import os |
| import subprocess |
| |
| |
| # Whether or not to show debug messages |
| DEBUG = False |
| |
| def notify(msg, *args): |
| """Print a message to stderr.""" |
| print >> sys.stderr, msg % args |
| |
| def debug (msg, *args): |
| """Print a debug message to stderr when DEBUG is enabled.""" |
| if DEBUG: |
| print >> sys.stderr, msg % args |
| |
| def error (msg, *args): |
| """Print an error message to stderr.""" |
| print >> sys.stderr, "ERROR:", msg % args |
| |
| def warn(msg, *args): |
| """Print a warning message to stderr.""" |
| print >> sys.stderr, "warning:", msg % args |
| |
| def die (msg, *args): |
| """Print as error message to stderr and exit the program.""" |
| error(msg, *args) |
| sys.exit(1) |
| |
| |
| class ProgressIndicator(object): |
| |
| """Simple progress indicator. |
| |
| Displayed as a spinning character by default, but can be customized |
| by passing custom messages that overrides the spinning character. |
| |
| """ |
| |
| States = ("|", "/", "-", "\\") |
| |
| def __init__ (self, prefix = "", f = sys.stdout): |
| """Create a new ProgressIndicator, bound to the given file object.""" |
| self.n = 0 # Simple progress counter |
| self.f = f # Progress is written to this file object |
| self.prev_len = 0 # Length of previous msg (to be overwritten) |
| self.prefix = prefix # Prefix prepended to each progress message |
| self.prefix_lens = [] # Stack of prefix string lengths |
| |
| def pushprefix (self, prefix): |
| """Append the given prefix onto the prefix stack.""" |
| self.prefix_lens.append(len(self.prefix)) |
| self.prefix += prefix |
| |
| def popprefix (self): |
| """Remove the last prefix from the prefix stack.""" |
| prev_len = self.prefix_lens.pop() |
| self.prefix = self.prefix[:prev_len] |
| |
| def __call__ (self, msg = None, lf = False): |
| """Indicate progress, possibly with a custom message.""" |
| if msg is None: |
| msg = self.States[self.n % len(self.States)] |
| msg = self.prefix + msg |
| print >> self.f, "\r%-*s" % (self.prev_len, msg), |
| self.prev_len = len(msg.expandtabs()) |
| if lf: |
| print >> self.f |
| self.prev_len = 0 |
| self.n += 1 |
| |
| def finish (self, msg = "done", noprefix = False): |
| """Finalize progress indication with the given message.""" |
| if noprefix: |
| self.prefix = "" |
| self(msg, True) |
| |
| |
| def start_command (args, cwd = None, shell = False, add_env = None, |
| stdin = subprocess.PIPE, stdout = subprocess.PIPE, |
| stderr = subprocess.PIPE): |
| """Start the given command, and return a subprocess object. |
| |
| This provides a simpler interface to the subprocess module. |
| |
| """ |
| env = None |
| if add_env is not None: |
| env = os.environ.copy() |
| env.update(add_env) |
| return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout, |
| stderr = stderr, cwd = cwd, shell = shell, |
| env = env, universal_newlines = True) |
| |
| |
| def run_command (args, cwd = None, shell = False, add_env = None, |
| flag_error = True): |
| """Run the given command to completion, and return its results. |
| |
| This provides a simpler interface to the subprocess module. |
| |
| The results are formatted as a 3-tuple: (exit_code, output, errors) |
| |
| If flag_error is enabled, Error messages will be produced if the |
| subprocess terminated with a non-zero exit code and/or stderr |
| output. |
| |
| The other arguments are passed on to start_command(). |
| |
| """ |
| process = start_command(args, cwd, shell, add_env) |
| (output, errors) = process.communicate() |
| exit_code = process.returncode |
| if flag_error and errors: |
| error("'%s' returned errors:\n---\n%s---", " ".join(args), errors) |
| if flag_error and exit_code: |
| error("'%s' returned exit code %i", " ".join(args), exit_code) |
| return (exit_code, output, errors) |
| |
| |
| def file_reader_method (missing_ok = False): |
| """Decorator for simplifying reading of files. |
| |
| If missing_ok is True, a failure to open a file for reading will |
| not raise the usual IOError, but instead the wrapped method will be |
| called with f == None. The method must in this case properly |
| handle f == None. |
| |
| """ |
| def _wrap (method): |
| """Teach given method to handle both filenames and file objects. |
| |
| The given method must take a file object as its second argument |
| (the first argument being 'self', of course). This decorator |
| will take a filename given as the second argument and promote |
| it to a file object. |
| |
| """ |
| def _wrapped_method (self, filename, *args, **kwargs): |
| if isinstance(filename, file): |
| f = filename |
| else: |
| try: |
| f = open(filename, 'r') |
| except IOError: |
| if missing_ok: |
| f = None |
| else: |
| raise |
| try: |
| return method(self, f, *args, **kwargs) |
| finally: |
| if not isinstance(filename, file) and f: |
| f.close() |
| return _wrapped_method |
| return _wrap |
| |
| |
| def file_writer_method (method): |
| """Decorator for simplifying writing of files. |
| |
| Enables the given method to handle both filenames and file objects. |
| |
| The given method must take a file object as its second argument |
| (the first argument being 'self', of course). This decorator will |
| take a filename given as the second argument and promote it to a |
| file object. |
| |
| """ |
| def _new_method (self, filename, *args, **kwargs): |
| if isinstance(filename, file): |
| f = filename |
| else: |
| # Make sure the containing directory exists |
| parent_dir = os.path.dirname(filename) |
| if not os.path.isdir(parent_dir): |
| os.makedirs(parent_dir) |
| f = open(filename, 'w') |
| try: |
| return method(self, f, *args, **kwargs) |
| finally: |
| if not isinstance(filename, file): |
| f.close() |
| return _new_method |