git_remote_helpers / util.pyon commit fast-import: document the --done option (3266de1)
   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:
  15    from subprocess import CalledProcessError
  16except ImportError:
  17    # from python2.7:subprocess.py
  18    # Exception classes used by this module.
  19    class CalledProcessError(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."""
  23        def __init__(self, returncode, cmd):
  24            self.returncode = returncode
  25            self.cmd = cmd
  26        def __str__(self):
  27            return "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
  33def notify(msg, *args):
  34    """Print a message to stderr."""
  35    print >> sys.stderr, msg % args
  36
  37def debug (msg, *args):
  38    """Print a debug message to stderr when DEBUG is enabled."""
  39    if DEBUG:
  40        print >> sys.stderr, msg % args
  41
  42def error (msg, *args):
  43    """Print an error message to stderr."""
  44    print >> sys.stderr, "ERROR:", msg % args
  45
  46def warn(msg, *args):
  47    """Print a warning message to stderr."""
  48    print >> sys.stderr, "warning:", msg % args
  49
  50def die (msg, *args):
  51    """Print as error message to stderr and exit the program."""
  52    error(msg, *args)
  53    sys.exit(1)
  54
  55
  56class ProgressIndicator(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
  67    def __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
  75    def pushprefix (self, prefix):
  76        """Append the given prefix onto the prefix stack."""
  77        self.prefix_lens.append(len(self.prefix))
  78        self.prefix += prefix
  79
  80    def popprefix (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
  85    def __call__ (self, msg = None, lf = False):
  86        """Indicate progress, possibly with a custom message."""
  87        if msg is None:
  88            msg = self.States[self.n % len(self.States)]
  89        msg = self.prefix + msg
  90        print >> self.f, "\r%-*s" % (self.prev_len, msg),
  91        self.prev_len = len(msg.expandtabs())
  92        if lf:
  93            print >> self.f
  94            self.prev_len = 0
  95        self.n += 1
  96
  97    def finish (self, msg = "done", noprefix = False):
  98        """Finalize progress indication with the given message."""
  99        if noprefix:
 100            self.prefix = ""
 101        self(msg, True)
 102
 103
 104def start_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
 113    if add_env is not None:
 114        env = os.environ.copy()
 115        env.update(add_env)
 116    return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout,
 117                            stderr = stderr, cwd = cwd, shell = shell,
 118                            env = env, universal_newlines = True)
 119
 120
 121def run_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
 139    if flag_error and errors:
 140        error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
 141    if flag_error and exit_code:
 142        error("'%s' returned exit code %i", " ".join(args), exit_code)
 143    return (exit_code, output, errors)
 144
 145
 146# from python2.7:subprocess.py
 147def call(*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    """
 155    return subprocess.Popen(*popenargs, **kwargs).wait()
 156
 157
 158# from python2.7:subprocess.py
 159def check_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)
 170    if retcode:
 171        cmd = kwargs.get("args")
 172        if cmd is None:
 173            cmd = popenargs[0]
 174        raise CalledProcessError(retcode, cmd)
 175    return 0
 176
 177
 178def file_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    """
 187    def _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        """
 196        def _wrapped_method (self, filename, *args, **kwargs):
 197            if isinstance(filename, file):
 198                f = filename
 199            else:
 200                try:
 201                    f = open(filename, 'r')
 202                except IOError:
 203                    if missing_ok:
 204                        f = None
 205                    else:
 206                        raise
 207            try:
 208                return method(self, f, *args, **kwargs)
 209            finally:
 210                if not isinstance(filename, file) and f:
 211                    f.close()
 212        return _wrapped_method
 213    return _wrap
 214
 215
 216def file_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    """
 227    def _new_method (self, filename, *args, **kwargs):
 228        if isinstance(filename, file):
 229            f = filename
 230        else:
 231            # Make sure the containing directory exists
 232            parent_dir = os.path.dirname(filename)
 233            if not os.path.isdir(parent_dir):
 234                os.makedirs(parent_dir)
 235            f = open(filename, 'w')
 236        try:
 237            return method(self, f, *args, **kwargs)
 238        finally:
 239            if not isinstance(filename, file):
 240                f.close()
 241    return _new_method