mirror of
https://github.com/git/git.git
synced 2024-11-26 11:34:00 +08:00
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
|