 6c8151a32e
			
		
	
	6c8151a32e
	
	
	
		
			
			Also add check_output from python 2.7. Signed-off-by: Sverre Rabbelier <srabbelier@gmail.com> Acked-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
		
			
				
	
	
		
			276 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/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
 | |
| 
 | |
| try:
 | |
|     from subprocess import CalledProcessError
 | |
| except ImportError:
 | |
|     # from python2.7:subprocess.py
 | |
|     # Exception classes used by this module.
 | |
|     class CalledProcessError(Exception):
 | |
|         """This exception is raised when a process run by check_call() returns
 | |
|         a non-zero exit status.  The exit status will be stored in the
 | |
|         returncode attribute."""
 | |
|         def __init__(self, returncode, cmd):
 | |
|             self.returncode = returncode
 | |
|             self.cmd = cmd
 | |
|         def __str__(self):
 | |
|             return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
 | |
| 
 | |
| 
 | |
| # 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)
 | |
| 
 | |
| 
 | |
| # from python2.7:subprocess.py
 | |
| def call(*popenargs, **kwargs):
 | |
|     """Run command with arguments.  Wait for command to complete, then
 | |
|     return the returncode attribute.
 | |
| 
 | |
|     The arguments are the same as for the Popen constructor.  Example:
 | |
| 
 | |
|     retcode = call(["ls", "-l"])
 | |
|     """
 | |
|     return subprocess.Popen(*popenargs, **kwargs).wait()
 | |
| 
 | |
| 
 | |
| # from python2.7:subprocess.py
 | |
| def check_call(*popenargs, **kwargs):
 | |
|     """Run command with arguments.  Wait for command to complete.  If
 | |
|     the exit code was zero then return, otherwise raise
 | |
|     CalledProcessError.  The CalledProcessError object will have the
 | |
|     return code in the returncode attribute.
 | |
| 
 | |
|     The arguments are the same as for the Popen constructor.  Example:
 | |
| 
 | |
|     check_call(["ls", "-l"])
 | |
|     """
 | |
|     retcode = call(*popenargs, **kwargs)
 | |
|     if retcode:
 | |
|         cmd = kwargs.get("args")
 | |
|         if cmd is None:
 | |
|             cmd = popenargs[0]
 | |
|         raise CalledProcessError(retcode, cmd)
 | |
|     return 0
 | |
| 
 | |
| 
 | |
| # from python2.7:subprocess.py
 | |
| def check_output(*popenargs, **kwargs):
 | |
|     r"""Run command with arguments and return its output as a byte string.
 | |
| 
 | |
|     If the exit code was non-zero it raises a CalledProcessError.  The
 | |
|     CalledProcessError object will have the return code in the returncode
 | |
|     attribute and output in the output attribute.
 | |
| 
 | |
|     The arguments are the same as for the Popen constructor.  Example:
 | |
| 
 | |
|     >>> check_output(["ls", "-l", "/dev/null"])
 | |
|     'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'
 | |
| 
 | |
|     The stdout argument is not allowed as it is used internally.
 | |
|     To capture standard error in the result, use stderr=STDOUT.
 | |
| 
 | |
|     >>> check_output(["/bin/sh", "-c",
 | |
|     ...               "ls -l non_existent_file ; exit 0"],
 | |
|     ...              stderr=STDOUT)
 | |
|     'ls: non_existent_file: No such file or directory\n'
 | |
|     """
 | |
|     if 'stdout' in kwargs:
 | |
|         raise ValueError('stdout argument not allowed, it will be overridden.')
 | |
|     process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
 | |
|     output, unused_err = process.communicate()
 | |
|     retcode = process.poll()
 | |
|     if retcode:
 | |
|         cmd = kwargs.get("args")
 | |
|         if cmd is None:
 | |
|             cmd = popenargs[0]
 | |
|         raise subprocess.CalledProcessError(retcode, cmd)
 | |
|     return output
 | |
| 
 | |
| 
 | |
| 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
 |