Prefer vim to vi for default editor (#4230)

* vim > vi
* Allow which to accept multiple args
* Update __init__ to use which with multiple args
* Fix doc tests
This commit is contained in:
Adam J. Stewart 2017-06-15 04:27:18 -05:00 committed by Todd Gamblin
parent ce11e78530
commit e627447417
7 changed files with 93 additions and 81 deletions

View File

@ -213,7 +213,22 @@
__all__ += spack.util.executable.__all__ __all__ += spack.util.executable.__all__
# User's editor from the environment # User's editor from the environment
editor = Executable(os.environ.get("EDITOR", "vi"))
# Default editors to use:
_default_editors = ['vim', 'vi', 'emacs', 'nano']
# The EDITOR environment variable has the highest precedence
if os.environ.get('EDITOR'):
_default_editors.insert(0, os.environ.get('EDITOR'))
editor = which(*_default_editors)
if not editor:
default = default_editors[0]
msg = 'Default text editor, {0}, not found.\n'.format(default)
msg += 'Please set the EDITOR environment variable to your preferred '
msg += 'text editor, or install {0}.'.format(default)
raise EnvironmentError(msg)
from spack.package import \ from spack.package import \
install_dependency_symlinks, flatten_dependencies, \ install_dependency_symlinks, flatten_dependencies, \

View File

@ -69,8 +69,7 @@ def _gcc_get_libstdcxx_version(self, version):
if Clang.default_version(rungcc.exe[0]) != 'unknown': if Clang.default_version(rungcc.exe[0]) != 'unknown':
return None return None
output = rungcc("--print-file-name=%s" % libname, output = rungcc("--print-file-name=%s" % libname, output=str)
return_output=True)
except ProcessError: except ProcessError:
return None return None
if not output: if not output:

View File

@ -39,7 +39,7 @@ def compile_c_and_execute(source_file, include_flags, link_flags):
*link_flags) *link_flags)
check = Executable('./check') check = Executable('./check')
return check(return_output=True) return check(output=str)
def compare_output(current_output, blessed_output): def compare_output(current_output, blessed_output):

View File

@ -46,9 +46,16 @@ def __init__(self, name):
raise ProcessError("Cannot construct executable for '%s'" % name) raise ProcessError("Cannot construct executable for '%s'" % name)
def add_default_arg(self, arg): def add_default_arg(self, arg):
"""Add a default argument to the command."""
self.exe.append(arg) self.exe.append(arg)
def add_default_env(self, key, value): def add_default_env(self, key, value):
"""Set an environment variable when the command is run.
Parameters:
key: The environment variable to set
value: The value to set it to
"""
self.default_env[key] = value self.default_env[key] = value
@property @property
@ -81,55 +88,34 @@ def path(self):
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
"""Run this executable in a subprocess. """Run this executable in a subprocess.
Arguments Parameters:
args *args (str): Command-line arguments to the executable to run
command line arguments to the executable to run.
Optional arguments Keyword Arguments:
env (dict): The environment to run the executable with
fail_on_error (bool): Raise an exception if the subprocess returns
an error. Default is True. The return code is available as
``exe.returncode``
ignore_errors (int or list): A list of error codes to ignore.
If these codes are returned, this process will not raise
an exception even if ``fail_on_error`` is set to ``True``
input: Where to read stdin from
output: Where to send stdout
error: Where to send stderr
fail_on_error Accepted values for input, output, and error:
Raise an exception if the subprocess returns an * python streams, e.g. open Python file objects, or ``os.devnull``
error. Default is True. When not set, the return code is * filenames, which will be automatically opened for writing
available as `exe.returncode`. * ``str``, as in the Python string type. If you set these to ``str``,
output and error will be written to pipes and returned as a string.
ignore_errors If both ``output`` and ``error`` are set to ``str``, then one string
is returned containing output concatenated with error. Not valid
An optional list/tuple of error codes that can be for ``input``
*ignored*. i.e., if these codes are returned, this will
not raise an exception when `fail_on_error` is `True`.
output, error
These arguments allow you to specify new stdout and stderr
values. They default to `None`, which means the
subprocess will inherit the parent's file descriptors.
You can set these to:
- python streams, e.g. open Python file objects, or os.devnull;
- filenames, which will be automatically opened for writing; or
- `str`, as in the Python string type. If you set these to `str`,
output and error will be written to pipes and returned as
a string. If both `output` and `error` are set to `str`,
then one string is returned containing output concatenated
with error.
input
Same as output, error, but `str` is not an allowed value.
Deprecated arguments
return_output[=False]
Setting this to True is the same as setting output=str.
This argument may be removed in future Spack versions.
By default, the subprocess inherits the parent's file descriptors.
""" """
fail_on_error = kwargs.pop("fail_on_error", True) # Environment
ignore_errors = kwargs.pop("ignore_errors", ())
# environment
env_arg = kwargs.get('env', None) env_arg = kwargs.get('env', None)
if env_arg is None: if env_arg is None:
env = os.environ.copy() env = os.environ.copy()
@ -138,19 +124,19 @@ def __call__(self, *args, **kwargs):
env = self.default_env.copy() env = self.default_env.copy()
env.update(env_arg) env.update(env_arg)
# TODO: This is deprecated. Remove in a future version. fail_on_error = kwargs.pop('fail_on_error', True)
return_output = kwargs.pop("return_output", False) ignore_errors = kwargs.pop('ignore_errors', ())
# Default values of None says to keep parent's file descriptors. # If they just want to ignore one error code, make it a tuple.
if return_output: if isinstance(ignore_errors, int):
output = str ignore_errors = (ignore_errors, )
else:
output = kwargs.pop("output", None) input = kwargs.pop('input', None)
output = kwargs.pop('output', None)
error = kwargs.pop('error', None)
error = kwargs.pop("error", None)
input = kwargs.pop("input", None)
if input is str: if input is str:
raise ValueError("Cannot use `str` as input stream.") raise ValueError('Cannot use `str` as input stream.')
def streamify(arg, mode): def streamify(arg, mode):
if isinstance(arg, string_types): if isinstance(arg, string_types):
@ -161,12 +147,8 @@ def streamify(arg, mode):
return arg, False return arg, False
ostream, close_ostream = streamify(output, 'w') ostream, close_ostream = streamify(output, 'w')
estream, close_estream = streamify(error, 'w') estream, close_estream = streamify(error, 'w')
istream, close_istream = streamify(input, 'r') istream, close_istream = streamify(input, 'r')
# if they just want to ignore one error code, make it a tuple.
if isinstance(ignore_errors, int):
ignore_errors = (ignore_errors, )
quoted_args = [arg for arg in args if re.search(r'^"|^\'|"$|\'$', arg)] quoted_args = [arg for arg in args if re.search(r'^"|^\'|"$|\'$', arg)]
if quoted_args: if quoted_args:
@ -196,7 +178,7 @@ def streamify(arg, mode):
rc = self.returncode = proc.returncode rc = self.returncode = proc.returncode
if fail_on_error and rc != 0 and (rc not in ignore_errors): if fail_on_error and rc != 0 and (rc not in ignore_errors):
raise ProcessError("Command exited with status %d:" % raise ProcessError('Command exited with status %d:' %
proc.returncode, cmd_line) proc.returncode, cmd_line)
if output is str or error is str: if output is str or error is str:
@ -209,12 +191,12 @@ def streamify(arg, mode):
except OSError as e: except OSError as e:
raise ProcessError( raise ProcessError(
"%s: %s" % (self.exe[0], e.strerror), "Command: " + cmd_line) '%s: %s' % (self.exe[0], e.strerror), 'Command: ' + cmd_line)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if fail_on_error: if fail_on_error:
raise ProcessError( raise ProcessError(
str(e), "\nExit status %d when invoking command: %s" % str(e), '\nExit status %d when invoking command: %s' %
(proc.returncode, cmd_line)) (proc.returncode, cmd_line))
finally: finally:
@ -235,27 +217,43 @@ def __hash__(self):
return hash((type(self), ) + tuple(self.exe)) return hash((type(self), ) + tuple(self.exe))
def __repr__(self): def __repr__(self):
return "<exe: %s>" % self.exe return '<exe: %s>' % self.exe
def __str__(self): def __str__(self):
return ' '.join(self.exe) return ' '.join(self.exe)
def which(name, **kwargs): def which(*args, **kwargs):
"""Finds an executable in the path like command-line which.""" """Finds an executable in the path like command-line which.
path = kwargs.get('path', os.environ.get('PATH', '').split(os.pathsep))
If given multiple executables, returns the first one that is found.
If no executables are found, returns None.
Parameters:
*args (str): One or more executables to search for
Keyword Arguments:
path (:func:`list` or str): The path to search. Defaults to ``PATH``
required (bool): If set to True, raise an error if executable not found
Returns:
Executable: The first executable that is found in the path
"""
path = kwargs.get('path', os.environ.get('PATH'))
required = kwargs.get('required', False) required = kwargs.get('required', False)
if not path: if isinstance(path, string_types):
path = [] path = path.split(os.pathsep)
for dir in path: for name in args:
exe = os.path.join(dir, name) for directory in path:
if os.path.isfile(exe) and os.access(exe, os.X_OK): exe = os.path.join(directory, name)
return Executable(exe) if os.path.isfile(exe) and os.access(exe, os.X_OK):
return Executable(exe)
if required: if required:
tty.die("spack requires %s. Make sure it is in your path." % name) tty.die("spack requires '%s'. Make sure it is in your path." % args[0])
return None return None

View File

@ -184,7 +184,7 @@ def check_install(self, spec):
"-L%s" % spec["hdf5"].prefix.lib, "-lhdf5") "-L%s" % spec["hdf5"].prefix.lib, "-lhdf5")
try: try:
check = Executable("./check") check = Executable("./check")
output = check(return_output=True) output = check(output=str)
except: except:
output = "" output = ""
success = output == expected success = output == expected

View File

@ -263,7 +263,7 @@ def check_install(self):
spec['hdf5'].libs.ld_flags.split())) spec['hdf5'].libs.ld_flags.split()))
try: try:
check = Executable('./check') check = Executable('./check')
output = check(return_output=True) output = check(output=str)
except: except:
output = "" output = ""
success = output == expected success = output == expected

View File

@ -72,7 +72,7 @@ def setup_environment(self, spack_env, run_env):
# spack_env.set('JAVA_HOME', self.spec['jdk'].prefix) # spack_env.set('JAVA_HOME', self.spec['jdk'].prefix)
hadoop = self.spec['hadoop'].command hadoop = self.spec['hadoop'].command
hadoop_classpath = hadoop('classpath', return_output=True) hadoop_classpath = hadoop('classpath', output=str)
# Remove whitespaces, as they can compromise syntax in # Remove whitespaces, as they can compromise syntax in
# module files # module files