Skip to content

Commit

Permalink
Merge pull request #4 from brandones/master
Browse files Browse the repository at this point in the history
Add 'die' parameter
  • Loading branch information
toastdriven committed Jun 8, 2017
2 parents 8d86e48 + 18707e0 commit 4e8fe3f
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
22 changes: 22 additions & 0 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,28 @@ If the command is interactive, you can send it input as well.::
a good option.


Failing Fast
------------

You can have non-zero exit codes propagate as exceptions::

>>> from shell import shell
>>> shell('ls /not/a/real/place', die=True)
Traceback (most recent call last):
...
shell.CommandError: Command exited with code 1
>>> import sys
>>> from shell import CommandError
>>> try:
>>> shell('ls /also/definitely/fake', die=True)
>>> except CommandError, e:
>>> print e.stderr
>>> sys.exit(e.code)
ls: /also/definitely/fake: No such file or directory
$ echo $?
1


Chaining
--------

Expand Down
28 changes: 24 additions & 4 deletions shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ class MissingCommandException(ShellException):

class CommandError(ShellException):
"""Thrown when a command fails."""
error_code = 1
def __init__(self, message, code, stderr):
self.message = message
self.code = code
self.stderr = stderr
super(CommandError, self).__init__(message)


class Shell(object):
Expand All @@ -66,13 +70,18 @@ class Shell(object):
Optionally accepts a ``strip_empty`` parameter, which should be a boolean.
If set to ``True``, only non-empty lines from ``Shell.output`` or
``Shell.errors`` will be returned. (Default: ``True``)
Optionally accepts a ``die`` parameter, which should be a boolean.
If set to ``True``, raises a CommandError if the command exits with a
non-zero return code. (Default: ``False``)
"""
def __init__(self, has_input=False, record_output=True, record_errors=True,
strip_empty=True):
strip_empty=True, die=False):
self.has_input = has_input
self.record_output = record_output
self.record_errors = record_errors
self.strip_empty = strip_empty
self.die = die

self.last_command = ''
self.line_breaks = '\n'
Expand Down Expand Up @@ -126,6 +135,12 @@ def _communicate(self, the_input=None):
if self._popen.returncode is not None:
self.code = self._popen.returncode

if self.die and self.code != 0:
raise CommandError(
message='Command exited with code {}'.format(self.code),
code=self.code,
stderr=stderr)

def run(self, command):
"""
Runs a given command.
Expand Down Expand Up @@ -277,7 +292,7 @@ def errors(self, raw=False):


def shell(command, has_input=False, record_output=True, record_errors=True,
strip_empty=True):
strip_empty=True, die=False):
"""
A convenient shortcut for running commands.
Expand All @@ -301,6 +316,10 @@ def shell(command, has_input=False, record_output=True, record_errors=True,
If set to ``True``, only non-empty lines from ``Shell.output`` or
``Shell.errors`` will be returned. (Default: ``True``)
Optionally accepts a ``die`` parameter, which should be a boolean.
If set to ``True``, raises a CommandError if the command exits with a
non-zero return code. (Default: ``False``)
Returns the ``Shell`` instance, which has been run with the given command.
Example::
Expand All @@ -315,6 +334,7 @@ def shell(command, has_input=False, record_output=True, record_errors=True,
has_input=has_input,
record_output=record_output,
record_errors=record_errors,
strip_empty=strip_empty
strip_empty=strip_empty,
die=die
)
return sh.run(command)
14 changes: 13 additions & 1 deletion tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
except ImportError:
import unittest

from shell import Shell, shell
from shell import CommandError, Shell, shell


class ShellTestCase(unittest.TestCase):
Expand Down Expand Up @@ -141,3 +141,15 @@ def test_chaining(self):

output = shell('cat -u', has_input=True).write('Hello, world!').output()
self.assertEqual(output, ['Hello, world!'])

def test_die(self):
sh = Shell(die=True)
with self.assertRaises(CommandError):
sh.run('ls /maybe/this/exists/on/windows/or/something/idk')
try:
no_die = shell('ls /other/fake/stuff/for/sure') # get the stderr
sh.run('ls /other/fake/stuff/for/sure')
except CommandError, e:
self.assertEqual(e.code, 1)
self.assertEqual(e.stderr, no_die.errors()[0] + os.linesep)

0 comments on commit 4e8fe3f

Please sign in to comment.