large refactoring of new code
diff --git a/nodes/__init__.py b/nodes/__init__.py
index 8b13789..9198922 100644
--- a/nodes/__init__.py
+++ b/nodes/__init__.py
@@ -1 +1 @@
-
+"this package contains node discovery code"
diff --git a/nodes/ceph.py b/nodes/ceph.py
index c5a9288..216f30d 100644
--- a/nodes/ceph.py
+++ b/nodes/ceph.py
@@ -1,111 +1,71 @@
 """ Collect data about ceph nodes"""
 import json
 
-import sh
 from node import Node
+from disk_perf_test_tool.ext_libs import sh
 
 
 def discover_ceph_node():
     """ Return list of ceph's nodes ips """
-    
     ips = {}
-    osd_list = get_osds_list()
-    osd_ips = get_osds_ips(osd_list)
+
+    osd_ips = get_osds_ips(get_osds_list())
     mon_ips = get_mons_or_mds_ips("mon")
     mds_ips = get_mons_or_mds_ips("mds")
+
     for ip in osd_ips:
         url = "ssh://%s" % ip
-        if url in ips:
-            ips[url].append("ceph-osd")
-        else:
-            ips[url] = ["ceph-osd"]
+        ips.setdefault(url, []).append("ceph-osd")
+
     for ip in mon_ips:
         url = "ssh://%s" % ip
-        if url in ips:
-            ips[url].append("ceph-mon")
-        else:
-            ips[url] = ["ceph-mon"]
+        ips.setdefault(url, []).append("ceph-mon")
+
     for ip in mds_ips:
         url = "ssh://%s" % ip
-        if url in ips:
-            ips[url].append("ceph-mds")
-        else:
-            ips[url] = ["ceph-mds"]
+        ips.setdefault(url, []).append("ceph-mds")
 
-    res = []
-    for url, roles in ips:
-        res.append(Node(ip=url, roles=list(roles)))
-
-    return res
-
-
-# internal services
-
-
-class CephException(Exception):
-    """ Exceptions from ceph call"""
-    pass
-
-class ParameterException(Exception):
-    """ Bad parameter in function"""
-    pass
+    return [Node(ip=url, roles=list(roles)) for url, roles in ips.items()]
 
 
 def get_osds_list():
     """ Get list of osds id"""
-    try:
-        res = sh.ceph.osd.ls()
-        osd_list = [osd_id
-                    for osd_id in res.split("\n") if osd_id != '']
-        return osd_list
-    except sh.CommandNotFound:
-        raise CephException("Ceph command not found")
-    except:
-        raise CephException("Ceph command 'osd ls' execution error")
+    return filter(None, sh.ceph.osd.ls().split("\n"))
 
 
 def get_mons_or_mds_ips(who):
     """ Return mon ip list
         :param who - "mon" or "mds" """
-    try:
-        ips = set()
-        if who == "mon":
-            res = sh.ceph.mon.dump()
-        elif who == "mds":
-            res = sh.ceph.mds.dump()
-        else:
-            raise ParameterException("'%s' in get_mons_or_mds_ips instead of mon/mds" % who)
+    if who == "mon":
+        res = sh.ceph.mon.dump()
+    elif who == "mds":
+        res = sh.ceph.mds.dump()
+    else:
+        raise ValueError(("'%s' in get_mons_or_mds_ips instead" +
+                          "of mon/mds") % who)
 
-        line_res = res.split("\n")
-        for line in line_res:
-            fields = line.split()
-            if len(fields) > 2 and who in fields[2]:
-                ips.add(fields[1].split(":")[0])
+    line_res = res.split("\n")
+    ips = set()
 
-        return ips
+    for line in line_res:
+        fields = line.split()
 
-    except sh.CommandNotFound:
-        raise CephException("Ceph command not found")
-    except ParameterException as e:
-        raise e
-    except:
-        mes = "Ceph command '%s dump' execution error" % who
-        raise CephException(mes)
+        # what does fields[1], fields[2] means?
+        # make this code looks like:
+        # SOME_MENINGFULL_VAR1, SOME_MENINGFULL_VAR2 = line.split()[1:3]
+
+        if len(fields) > 2 and who in fields[2]:
+            ips.add(fields[1].split(":")[0])
+
+    return ips
 
 
 def get_osds_ips(osd_list):
-    """ Get osd's ips 
+    """ Get osd's ips
         :param osd_list - list of osd names from osd ls command"""
-    try:
-        ips = set()
-        for osd_id in osd_list:
-            res = sh.ceph.osd.find(osd_id)
-            ip = json.loads(str(res))["ip"]
-            ips.add(ip.split(":")[0])
-        return ips
-
-    except sh.CommandNotFound:
-        raise CephException("Ceph command not found")
-    except:
-        raise CephException("Ceph command 'osd find' execution error")
-
+    ips = set()
+    for osd_id in osd_list:
+        res = sh.ceph.osd.find(osd_id)
+        ip = json.loads(str(res))["ip"]
+        ips.add(ip.split(":")[0])
+    return ips
diff --git a/nodes/discover.py b/nodes/discover.py
index 17aa1e7..d36e517 100644
--- a/nodes/discover.py
+++ b/nodes/discover.py
@@ -1,9 +1,10 @@
 import logging
 
-import openstack
 import ceph
 import fuel
+import openstack
 
+from disk_perf_test_tool.utils import parse_creds
 
 logger = logging.getLogger("io-perf-tool")
 
@@ -15,23 +16,37 @@
     nodes_to_run = []
     for cluster, cluster_info in cluster_conf.items():
         if cluster == "openstack":
-            conn = cluster_info.get('connection')
+
+            conn = cluster_info['connection']
+            user, passwd, tenant = parse_creds(conn['creds'])
+
+            auth_data = dict(
+                auth_url=conn['auth_url'],
+                username=user,
+                api_key=passwd,
+                project_id=tenant)
+
             if not conn:
                 logger.error("No connection provided for %s. Skipping"
                              % cluster)
                 continue
+
             logger.debug("Discovering openstack nodes "
                          "with connection details: %r" %
                          conn)
 
-            nodes_to_run.extend(openstack.discover_openstack_nodes(
-                conn, cluster_info))
+            os_nodes = openstack.discover_openstack_nodes(auth_data,
+                                                          cluster_info)
+            nodes_to_run.extend(os_nodes)
+
         if cluster == "fuel":
             url = cluster_info['connection'].pop('url')
             creads = cluster_info['connection']
             roles = cluster_info['discover']
+
             if isinstance(roles, basestring):
                 roles = [roles]
+
             nodes_to_run.extend(fuel.discover_fuel_nodes(url, creads, roles))
 
         if cluster == "ceph":
diff --git a/nodes/fuel.py b/nodes/fuel.py
index c828793..25476dc 100644
--- a/nodes/fuel.py
+++ b/nodes/fuel.py
@@ -1,6 +1,9 @@
+import logging
+
+
 import node
 import fuel_rest_api
-import logging
+from disk_perf_test_tool.utils import parse_creds
 
 
 logger = logging.getLogger("io-perf-tool")
@@ -8,10 +11,18 @@
 
 def discover_fuel_nodes(root_url, credentials, roles):
     """Discover Fuel nodes"""
-    connection = fuel_rest_api.KeystoneAuth(root_url, credentials)
+    user, passwd, tenant = parse_creds(credentials['creds'])
+
+    creds = dict(
+        username=user,
+        password=passwd,
+        tenant_name=tenant,
+    )
+
+    connection = fuel_rest_api.KeystoneAuth(root_url, creds)
     fi = fuel_rest_api.FuelInfo(connection)
     nodes = []
     for role in roles:
         nodes.extend(getattr(fi.nodes, role))
     logger.debug("Found %s fuel nodes" % len(fi.nodes))
-    return [node.Node(n.ip, n.get_roles()) for n in nodes]
\ No newline at end of file
+    return [node.Node(n.ip, n.get_roles()) for n in nodes]
diff --git a/nodes/node.py b/nodes/node.py
index 03efd34..d7cae40 100644
--- a/nodes/node.py
+++ b/nodes/node.py
@@ -8,9 +8,14 @@
         self.password = password
         self.port = port
         self.key_path = key_path
+        self.connection = None
+
+    def __str__(self):
+        return "<Node: url={0!r} roles={1} >".format(self.ip,
+                                                     ", ".join(self.roles))
 
     def __repr__(self):
-        return "<Node: %s %s>" % (self.ip, self.roles)
+        return str(self)
 
     def set_conn_attr(self, name, value):
         setattr(self, name, value)
diff --git a/nodes/openstack.py b/nodes/openstack.py
index ed3d8ea..558985d 100644
--- a/nodes/openstack.py
+++ b/nodes/openstack.py
@@ -1,8 +1,10 @@
 import logging
-import node
-from starts_vms import create_vms_mt
+
 from novaclient.client import Client
 
+import node
+from disk_perf_test_tool.utils import parse_creds
+
 
 logger = logging.getLogger("io-perf-tool")
 
@@ -16,10 +18,8 @@
 
 
 def discover_vms(client, search_opts):
-    auth = search_opts.pop('auth', {})
-    user = auth.get('user')
-    password = auth.get('password')
-    key = auth.get('key_file')
+    user, password, key = parse_creds(search_opts.pop('auth'))
+
     servers = client.servers.list(search_opts=search_opts)
     logger.debug("Found %s openstack vms" % len(servers))
     return [node.Node(get_floating_ip(server), ["test_vm"], username=user,
@@ -28,10 +28,8 @@
 
 
 def discover_services(client, opts):
-    auth = opts.pop('auth', {})
-    user = auth.get('user')
-    password = auth.get('password')
-    key = auth.get('key_file')
+    user, password, key = parse_creds(opts.pop('auth'))
+
     services = []
     if opts['service'] == "all":
         services = client.services.list()
@@ -69,36 +67,34 @@
         services_to_discover = conf['discover'].get('nodes')
         if services_to_discover:
             nodes.extend(discover_services(client, services_to_discover))
-    if conf.get('start'):
-        vms = start_test_vms(client, conf['start'])
-        nodes.extend(vms)
 
     return nodes
 
 
-def start_test_vms(client, opts):
+# from disk_perf_test_tool.starts_vms import create_vms_mt
+# def start_test_vms(client, opts):
 
-    user = opts.pop("user", None)
-    key_file = opts.pop("key_file", None)
-    aff_group = opts.pop("aff_group", None)
-    raw_count = opts.pop('count')
+#     user = opts.pop("user", None)
+#     key_file = opts.pop("key_file", None)
+#     aff_group = opts.pop("aff_group", None)
+#     raw_count = opts.pop('count')
 
-    if raw_count.startswith("x"):
-        logger.debug("Getting amount of compute services")
-        count = len(client.services.list(binary="nova-compute"))
-        count *= int(raw_count[1:])
-    else:
-        count = int(raw_count)
+#     if raw_count.startswith("x"):
+#         logger.debug("Getting amount of compute services")
+#         count = len(client.services.list(binary="nova-compute"))
+#         count *= int(raw_count[1:])
+#     else:
+#         count = int(raw_count)
 
-    if aff_group is not None:
-        scheduler_hints = {'group': aff_group}
-    else:
-        scheduler_hints = None
+#     if aff_group is not None:
+#         scheduler_hints = {'group': aff_group}
+#     else:
+#         scheduler_hints = None
 
-    opts['scheduler_hints'] = scheduler_hints
+#     opts['scheduler_hints'] = scheduler_hints
 
-    logger.debug("Will start {0} vms".format(count))
+#     logger.debug("Will start {0} vms".format(count))
 
-    nodes = create_vms_mt(client, count, **opts)
-    return [node.Node(get_floating_ip(server), ["test_vm"], username=user,
-                      key_path=key_file) for server in nodes]
+#     nodes = create_vms_mt(client, count, **opts)
+#     return [node.Node(get_floating_ip(server), ["test_vm"], username=user,
+#                       key_path=key_file) for server in nodes]
diff --git a/nodes/sh.py b/nodes/sh.py
deleted file mode 100644
index fb0f5fe..0000000
--- a/nodes/sh.py
+++ /dev/null
@@ -1,2352 +0,0 @@
-"""
-http://amoffat.github.io/sh/
-"""
-#===============================================================================
-# Copyright (C) 2011-2015 by Andrew Moffat
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#===============================================================================
-
-
-__version__ = "1.11"
-__project_url__ = "https://github.com/amoffat/sh"
-
-
-
-import platform
-
-if "windows" in platform.system().lower():
-    raise ImportError("sh %s is currently only supported on linux and osx. \
-please install pbs 0.110 (http://pypi.python.org/pypi/pbs) for windows \
-support." % __version__)
-
-
-import sys
-IS_PY3 = sys.version_info[0] == 3
-
-import traceback
-import os
-import re
-from glob import glob as original_glob
-import time
-from types import ModuleType
-from functools import partial
-import inspect
-from contextlib import contextmanager
-
-from locale import getpreferredencoding
-DEFAULT_ENCODING = getpreferredencoding() or "UTF-8"
-
-
-if IS_PY3:
-    from io import StringIO
-    from io import BytesIO as cStringIO
-    from queue import Queue, Empty
-
-    # for some reason, python 3.1 removed the builtin "callable", wtf
-    if not hasattr(__builtins__, "callable"):
-        def callable(ob):
-            return hasattr(ob, "__call__")
-else:
-    from StringIO import StringIO
-    from cStringIO import OutputType as cStringIO
-    from Queue import Queue, Empty
-
-IS_OSX = platform.system() == "Darwin"
-THIS_DIR = os.path.dirname(os.path.realpath(__file__))
-SH_LOGGER_NAME = "sh"
-
-
-import errno
-import warnings
-
-import pty
-import termios
-import signal
-import gc
-import select
-import threading
-import tty
-import fcntl
-import struct
-import resource
-from collections import deque
-import logging
-import weakref
-
-
-# TODO remove with contexts in next version
-def with_context_warning():
-    warnings.warn("""
-with contexts are deprecated because they are not thread safe.  they will be \
-removed in the next version.  use subcommands instead \
-http://amoffat.github.io/sh/#sub-commands. see \
-https://github.com/amoffat/sh/issues/195
-""".strip(), stacklevel=3)
-
-
-
-if IS_PY3:
-    raw_input = input
-    unicode = str
-    basestring = str
-
-
-_unicode_methods = set(dir(unicode()))
-
-
-def encode_to_py3bytes_or_py2str(s):
-    """ takes anything and attempts to return a py2 string or py3 bytes.  this
-    is typically used when creating command + arguments to be executed via
-    os.exec* """
-
-    fallback_encoding = "utf8"
-
-    if IS_PY3:
-        # if we're already bytes, do nothing
-        if isinstance(s, bytes):
-            pass
-        else:
-            s = str(s)
-            try:
-                s = bytes(s, DEFAULT_ENCODING)
-            except UnicodeEncodeError:
-                s = bytes(s, fallback_encoding)
-    else:
-        # attempt to convert the thing to unicode from the system's encoding
-        try:
-            s = unicode(s, DEFAULT_ENCODING)
-        # if the thing is already unicode, or it's a number, it can't be
-        # coerced to unicode with an encoding argument, but if we leave out
-        # the encoding argument, it will convert it to a string, then to unicode
-        except TypeError:
-            s = unicode(s)
-
-        # now that we have guaranteed unicode, encode to our system encoding,
-        # but attempt to fall back to something
-        try:
-            s = s.encode(DEFAULT_ENCODING)
-        except:
-            s = s.encode(fallback_encoding)
-    return s
-
-
-class ErrorReturnCode(Exception):
-    """ base class for all exceptions as a result of a command's exit status
-    being deemed an error.  this base class is dynamically subclassed into
-    derived classes with the format: ErrorReturnCode_NNN where NNN is the exit
-    code number.  the reason for this is it reduces boiler plate code when
-    testing error return codes:
-    
-        try:
-            some_cmd()
-        except ErrorReturnCode_12:
-            print("couldn't do X")
-            
-    vs:
-        try:
-            some_cmd()
-        except ErrorReturnCode as e:
-            if e.exit_code == 12:
-                print("couldn't do X")
-    
-    it's not much of a savings, but i believe it makes the code easier to read """
-
-    truncate_cap = 750
-
-    def __init__(self, full_cmd, stdout, stderr):
-        self.full_cmd = full_cmd
-        self.stdout = stdout
-        self.stderr = stderr
-
-
-        if self.stdout is None:
-            exc_stdout = "<redirected>"
-        else:
-            exc_stdout = self.stdout[:self.truncate_cap]
-            out_delta = len(self.stdout) - len(exc_stdout)
-            if out_delta:
-                exc_stdout += ("... (%d more, please see e.stdout)" % out_delta).encode()
-
-        if self.stderr is None:
-            exc_stderr = "<redirected>"
-        else:
-            exc_stderr = self.stderr[:self.truncate_cap]
-            err_delta = len(self.stderr) - len(exc_stderr)
-            if err_delta:
-                exc_stderr += ("... (%d more, please see e.stderr)" % err_delta).encode()
-
-        msg = "\n\n  RAN: %r\n\n  STDOUT:\n%s\n\n  STDERR:\n%s" % \
-            (full_cmd, exc_stdout.decode(DEFAULT_ENCODING, "replace"),
-             exc_stderr.decode(DEFAULT_ENCODING, "replace"))
-        super(ErrorReturnCode, self).__init__(msg)
-
-
-class SignalException(ErrorReturnCode): pass
-class TimeoutException(Exception):
-    """ the exception thrown when a command is killed because a specified
-    timeout (via _timeout) was hit """
-    def __init__(self, exit_code):
-        self.exit_code = exit_code
-        super(Exception, self).__init__()
-
-SIGNALS_THAT_SHOULD_THROW_EXCEPTION = (
-    signal.SIGABRT,
-    signal.SIGBUS,
-    signal.SIGFPE,
-    signal.SIGILL,
-    signal.SIGINT,
-    signal.SIGKILL,
-    signal.SIGPIPE,
-    signal.SIGQUIT,
-    signal.SIGSEGV,
-    signal.SIGTERM,
-    signal.SIGSYS,
-)
-
-
-# we subclass AttributeError because:
-# https://github.com/ipython/ipython/issues/2577
-# https://github.com/amoffat/sh/issues/97#issuecomment-10610629
-class CommandNotFound(AttributeError): pass
-
-
-
-
-rc_exc_regex = re.compile("(ErrorReturnCode|SignalException)_((\d+)|SIG\w+)")
-rc_exc_cache = {}
-
-
-def get_exc_from_name(name):
-    """ takes an exception name, like:
-
-        ErrorReturnCode_1
-        SignalException_9
-        SignalException_SIGHUP
-
-    and returns the corresponding exception.  this is primarily used for
-    importing exceptions from sh into user code, for instance, to capture those
-    exceptions """
-
-    exc = None
-    try:
-        return rc_exc_cache[name]
-    except KeyError:
-        m = rc_exc_regex.match(name)
-        if m:
-            base = m.group(1)
-            rc_or_sig_name = m.group(2)
-
-            if base == "SignalException":
-                try:
-                    rc = -int(rc_or_sig_name)
-                except ValueError:
-                    rc = -getattr(signal, rc_or_sig_name)
-            else:
-                rc = int(rc_or_sig_name)
-
-            exc = get_rc_exc(rc)
-    return exc
-
-
-def get_rc_exc(rc_or_sig_name):
-    """ takes a exit code, signal number, or signal name, and produces an
-    exception that corresponds to that return code.  positive return codes yield
-    ErrorReturnCode exception, negative return codes yield SignalException
-
-    we also cache the generated exception so that only one signal of that type
-    exists, preserving identity """
-
-    try:
-        rc = int(rc_or_sig_name)
-    except ValueError:
-        rc = -getattr(signal, rc_or_sig_name)
-
-    try:
-        return rc_exc_cache[rc]
-    except KeyError:
-        pass
-
-    if rc > 0:
-        name = "ErrorReturnCode_%d" % rc
-        base = ErrorReturnCode
-    else:
-        name = "SignalException_%d" % abs(rc)
-        base = SignalException
-
-    exc = type(name, (base,), {"exit_code": rc})
-    rc_exc_cache[rc] = exc
-    return exc
-
-
-
-
-def which(program):
-    def is_exe(fpath):
-        return (os.path.exists(fpath) and
-                os.access(fpath, os.X_OK) and
-                os.path.isfile(os.path.realpath(fpath)))
-
-    fpath, fname = os.path.split(program)
-    if fpath:
-        if is_exe(program):
-            return program
-    else:
-        if "PATH" not in os.environ:
-            return None
-        for path in os.environ["PATH"].split(os.pathsep):
-            exe_file = os.path.join(path, program)
-            if is_exe(exe_file):
-                return exe_file
-
-    return None
-
-def resolve_program(program):
-    path = which(program)
-    if not path:
-        # our actual command might have a dash in it, but we can't call
-        # that from python (we have to use underscores), so we'll check
-        # if a dash version of our underscore command exists and use that
-        # if it does
-        if "_" in program:
-            path = which(program.replace("_", "-"))
-        if not path:
-            return None
-    return path
-
-
-# we add this thin wrapper to glob.glob because of a specific edge case where
-# glob does not expand to anything.  for example, if you try to do
-# glob.glob("*.py") and there are no *.py files in the directory, glob.glob
-# returns an empty list.  this empty list gets passed to the command, and
-# then the command fails with a misleading error message.  this thin wrapper
-# ensures that if there is no expansion, we pass in the original argument,
-# so that when the command fails, the error message is clearer
-def glob(arg):
-    return original_glob(arg) or arg
-
-
-
-class Logger(object):
-    """ provides a memory-inexpensive logger.  a gotcha about python's builtin
-    logger is that logger objects are never garbage collected.  if you create a
-    thousand loggers with unique names, they'll sit there in memory until your
-    script is done.  with sh, it's easy to create loggers with unique names if
-    we want our loggers to include our command arguments.  for example, these
-    are all unique loggers:
-        
-            ls -l
-            ls -l /tmp
-            ls /tmp
-
-    so instead of creating unique loggers, and without sacrificing logging
-    output, we use this class, which maintains as part of its state, the logging
-    "context", which will be the very unique name.  this allows us to get a
-    logger with a very general name, eg: "command", and have a unique name
-    appended to it via the context, eg: "ls -l /tmp" """
-    def __init__(self, name, context=None):
-        self.name = name
-        if context:
-            context = context.replace("%", "%%")
-        self.context = context 
-        self.log = logging.getLogger("%s.%s" % (SH_LOGGER_NAME, name))
-
-    def _format_msg(self, msg, *args):
-        if self.context:
-            msg = "%s: %s" % (self.context, msg)
-        return msg % args
-
-    def get_child(self, name, context):
-        new_name = self.name + "." + name
-        new_context = self.context + "." + context
-        l = Logger(new_name, new_context)
-        return l
-
-    def info(self, msg, *args):
-        self.log.info(self._format_msg(msg, *args))
-
-    def debug(self, msg, *args):
-        self.log.debug(self._format_msg(msg, *args))
-
-    def error(self, msg, *args):
-        self.log.error(self._format_msg(msg, *args))
-
-    def exception(self, msg, *args):
-        self.log.exception(self._format_msg(msg, *args))
-
-
-def friendly_truncate(s, max_len):
-    if len(s) > max_len:
-        s = "%s...(%d more)" % (s[:max_len], len(s) - max_len)
-    return s
-
-
-class RunningCommand(object):
-    """ this represents an executing Command object.  it is returned as the
-    result of __call__() being executed on a Command instance.  this creates a
-    reference to a OProc instance, which is a low-level wrapper around the
-    process that was exec'd
-
-    this is the class that gets manipulated the most by user code, and so it
-    implements various convenience methods and logical mechanisms for the
-    underlying process.  for example, if a user tries to access a
-    backgrounded-process's stdout/err, the RunningCommand object is smart enough
-    to know to wait() on the process to finish first.  and when the process
-    finishes, RunningCommand is smart enough to translate exit codes to
-    exceptions. """
-
-    def __init__(self, cmd, call_args, stdin, stdout, stderr):
-        # self.ran is used for auditing what actually ran.  for example, in
-        # exceptions, or if you just want to know what was ran after the
-        # command ran
-        if IS_PY3:
-            self.ran = " ".join([arg.decode(DEFAULT_ENCODING, "ignore") for arg in cmd])
-        else:
-            self.ran = " ".join(cmd)
-
-
-        friendly_cmd = friendly_truncate(self.ran, 20)
-        friendly_call_args = friendly_truncate(str(call_args), 20)
-
-        # we're setting up the logger string here, instead of __repr__ because
-        # we reserve __repr__ to behave as if it was evaluating the child
-        # process's output
-        logger_str = "<Command %r call_args %s>" % (friendly_cmd,
-                friendly_call_args)
-
-        self.log = Logger("command", logger_str)
-        self.call_args = call_args
-        self.cmd = cmd
-
-        self.process = None
-        self._process_completed = False
-        should_wait = True
-        spawn_process = True
-
-
-        # with contexts shouldn't run at all yet, they prepend
-        # to every command in the context
-        if call_args["with"]:
-            spawn_process = False
-            Command._prepend_stack.append(self)
-
-
-        if call_args["piped"] or call_args["iter"] or call_args["iter_noblock"]:
-            should_wait = False
-
-        # we're running in the background, return self and let us lazily
-        # evaluate
-        if call_args["bg"]:
-            should_wait = False
-
-        # redirection
-        if call_args["err_to_out"]:
-            stderr = OProc.STDOUT
-
-
-        # set up which stream should write to the pipe
-        # TODO, make pipe None by default and limit the size of the Queue
-        # in oproc.OProc
-        pipe = OProc.STDOUT
-        if call_args["iter"] == "out" or call_args["iter"] is True:
-            pipe = OProc.STDOUT
-        elif call_args["iter"] == "err":
-            pipe = OProc.STDERR
-
-        if call_args["iter_noblock"] == "out" or call_args["iter_noblock"] is True:
-            pipe = OProc.STDOUT
-        elif call_args["iter_noblock"] == "err":
-            pipe = OProc.STDERR
-
-
-        # there's currently only one case where we wouldn't spawn a child
-        # process, and that's if we're using a with-context with our command
-        if spawn_process:
-            self.log.info("starting process")
-            self.process = OProc(self.log, cmd, stdin, stdout, stderr,
-                    self.call_args, pipe)
-
-            if should_wait:
-                self.wait()
-
-
-    def wait(self):
-        if not self._process_completed:
-            self._process_completed = True
-
-            exit_code = self.process.wait()
-            if self.process.timed_out:
-                # if we timed out, our exit code represents a signal, which is
-                # negative, so let's make it positive to store in our
-                # TimeoutException
-                raise TimeoutException(-exit_code)
-            else:
-                self.handle_command_exit_code(exit_code)
-
-            # https://github.com/amoffat/sh/issues/185
-            if self.call_args["done"]:
-                self.call_args["done"](self)
-
-        return self
-
-
-    def handle_command_exit_code(self, code):
-        """ here we determine if we had an exception, or an error code that we
-        weren't expecting to see.  if we did, we create and raise an exception
-        """
-        if (code not in self.call_args["ok_code"] and (code > 0 or -code in
-            SIGNALS_THAT_SHOULD_THROW_EXCEPTION)):
-            exc = get_rc_exc(code)
-            raise exc(self.ran, self.process.stdout, self.process.stderr)
-
-
-
-    @property
-    def stdout(self):
-        self.wait()
-        return self.process.stdout
-
-    @property
-    def stderr(self):
-        self.wait()
-        return self.process.stderr
-
-    @property
-    def exit_code(self):
-        self.wait()
-        return self.process.exit_code
-
-    @property
-    def pid(self):
-        return self.process.pid
-
-    def __len__(self):
-        return len(str(self))
-
-    def __enter__(self):
-        """ we don't actually do anything here because anything that should have
-        been done would have been done in the Command.__call__ call.
-        essentially all that has to happen is the comand be pushed on the
-        prepend stack. """
-        with_context_warning()
-
-    def __iter__(self):
-        return self
-
-    def next(self):
-        """ allow us to iterate over the output of our command """
-
-        # we do this because if get blocks, we can't catch a KeyboardInterrupt
-        # so the slight timeout allows for that.
-        while True:
-            try:
-                chunk = self.process._pipe_queue.get(True, 0.001)
-            except Empty:
-                if self.call_args["iter_noblock"]:
-                    return errno.EWOULDBLOCK
-            else:
-                if chunk is None:
-                    self.wait()
-                    raise StopIteration()
-                try:
-                    return chunk.decode(self.call_args["encoding"],
-                        self.call_args["decode_errors"])
-                except UnicodeDecodeError:
-                    return chunk
-
-    # python 3
-    __next__ = next
-
-    def __exit__(self, typ, value, traceback):
-        if self.call_args["with"] and Command._prepend_stack:
-            Command._prepend_stack.pop()
-
-    def __str__(self):
-        """ in python3, should return unicode.  in python2, should return a
-        string of bytes """
-        if IS_PY3:
-            return self.__unicode__()
-        else:
-            return unicode(self).encode(self.call_args["encoding"])
-
-    def __unicode__(self):
-        """ a magic method defined for python2.  calling unicode() on a
-        RunningCommand object will call this """
-        if self.process and self.stdout:
-            return self.stdout.decode(self.call_args["encoding"],
-                self.call_args["decode_errors"])
-        elif IS_PY3:
-            return ""
-        else:
-            return unicode("")
-
-    def __eq__(self, other):
-        return unicode(self) == unicode(other)
-    __hash__ = None  # Avoid DeprecationWarning in Python < 3
-
-    def __contains__(self, item):
-        return item in str(self)
-
-    def __getattr__(self, p):
-        # let these three attributes pass through to the OProc object
-        if p in ("signal", "terminate", "kill"):
-            if self.process:
-                return getattr(self.process, p)
-            else:
-                raise AttributeError
-
-        # see if strings have what we're looking for.  we're looking at the
-        # method names explicitly because we don't want to evaluate self unless
-        # we absolutely have to, the reason being, in python2, hasattr swallows
-        # exceptions, and if we try to run hasattr on a command that failed and
-        # is being run with _iter=True, the command will be evaluated, throw an
-        # exception, but hasattr will discard it
-        if p in _unicode_methods:
-            return getattr(unicode(self), p)
-
-        raise AttributeError
-
-    def __repr__(self):
-        """ in python3, should return unicode.  in python2, should return a
-        string of bytes """
-        try:
-            return str(self)
-        except UnicodeDecodeError:
-            if self.process:
-                if self.stdout:
-                    return repr(self.stdout)
-            return repr("")
-
-    def __long__(self):
-        return long(str(self).strip())
-
-    def __float__(self):
-        return float(str(self).strip())
-
-    def __int__(self):
-        return int(str(self).strip())
-
-
-
-def output_redirect_is_filename(out):
-    return out \
-        and not callable(out) \
-        and not hasattr(out, "write") \
-        and not isinstance(out, (cStringIO, StringIO))
-
-
-
-
-
-
-class Command(object):
-    """ represents an un-run system program, like "ls" or "cd".  because it
-    represents the program itself (and not a running instance of it), it should
-    hold very little state.  in fact, the only state it does hold is baked
-    arguments.
-    
-    when a Command object is called, the result that is returned is a
-    RunningCommand object, which represents the Command put into an execution
-    state. """
-    _prepend_stack = []
-
-    _call_args = {
-        # currently unsupported
-        #"fg": False, # run command in foreground
-
-        # run a command in the background.  commands run in the background
-        # ignore SIGHUP and do not automatically exit when the parent process
-        # ends
-        "bg": False,
-
-        "with": False, # prepend the command to every command after it
-        "in": None,
-        "out": None, # redirect STDOUT
-        "err": None, # redirect STDERR
-        "err_to_out": None, # redirect STDERR to STDOUT
-
-        # stdin buffer size
-        # 1 for line, 0 for unbuffered, any other number for that amount
-        "in_bufsize": 0,
-        # stdout buffer size, same values as above
-        "out_bufsize": 1,
-        "err_bufsize": 1,
-
-        # this is how big the output buffers will be for stdout and stderr.
-        # this is essentially how much output they will store from the process.
-        # we use a deque, so if it overflows past this amount, the first items
-        # get pushed off as each new item gets added.
-        #
-        # NOTICE
-        # this is not a *BYTE* size, this is a *CHUNK* size...meaning, that if
-        # you're buffering out/err at 1024 bytes, the internal buffer size will
-        # be "internal_bufsize" CHUNKS of 1024 bytes
-        "internal_bufsize": 3 * 1024 ** 2,
-
-        "env": None,
-        "piped": None,
-        "iter": None,
-        "iter_noblock": None,
-        "ok_code": 0,
-        "cwd": None,
-
-        # the separator delimiting between a long-argument's name and its value
-        # for example, --arg=derp, '=' is the long_sep
-        "long_sep": "=",
-
-        # this is for programs that expect their input to be from a terminal.
-        # ssh is one of those programs
-        "tty_in": False,
-        "tty_out": True,
-
-        "encoding": DEFAULT_ENCODING,
-        "decode_errors": "strict",
-
-        # how long the process should run before it is auto-killed
-        "timeout": 0,
-        "timeout_signal": signal.SIGKILL,
-
-        # TODO write some docs on "long-running processes"
-        # these control whether or not stdout/err will get aggregated together
-        # as the process runs.  this has memory usage implications, so sometimes
-        # with long-running processes with a lot of data, it makes sense to
-        # set these to true
-        "no_out": False,
-        "no_err": False,
-        "no_pipe": False,
-
-        # if any redirection is used for stdout or stderr, internal buffering
-        # of that data is not stored.  this forces it to be stored, as if
-        # the output is being T'd to both the redirected destination and our
-        # internal buffers
-        "tee": None,
-
-        # will be called when a process terminates without exception.  this
-        # option also puts the command in the background, since it doesn't make
-        # sense to have an un-backgrounded command with a done callback
-        "done": None,
-
-        # a tuple (rows, columns) of the desired size of both the stdout and
-        # stdin ttys, if ttys are being used
-        "tty_size": (20, 80),
-    }
-
-    # these are arguments that cannot be called together, because they wouldn't
-    # make any sense
-    _incompatible_call_args = (
-        #("fg", "bg", "Command can't be run in the foreground and background"),
-        ("err", "err_to_out", "Stderr is already being redirected"),
-        ("piped", "iter", "You cannot iterate when this command is being piped"),
-        ("piped", "no_pipe", "Using a pipe doesn't make sense if you've \
-disabled the pipe"),
-        ("no_out", "iter", "You cannot iterate over output if there is no \
-output"),
-    )
-
-
-    # this method exists because of the need to have some way of letting
-    # manual object instantiation not perform the underscore-to-dash command
-    # conversion that resolve_program uses.
-    #
-    # there are 2 ways to create a Command object.  using sh.Command(<program>)
-    # or by using sh.<program>.  the method fed into sh.Command must be taken
-    # literally, and so no underscore-dash conversion is performed.  the one
-    # for sh.<program> must do the underscore-dash converesion, because we
-    # can't type dashes in method names
-    @classmethod
-    def _create(cls, program, **default_kwargs):
-        path = resolve_program(program)
-        if not path:
-            raise CommandNotFound(program)
-
-        cmd = cls(path)
-        if default_kwargs:
-            cmd = cmd.bake(**default_kwargs)
-
-        return cmd
-
-
-    def __init__(self, path):
-        found = which(path)
-        if not found:
-            raise CommandNotFound(path)
-
-        self._path = encode_to_py3bytes_or_py2str(found) 
-
-        self._partial = False
-        self._partial_baked_args = []
-        self._partial_call_args = {}
-
-        # bugfix for functools.wraps.  issue #121
-        self.__name__ = str(self)
-
-
-    def __getattribute__(self, name):
-        # convenience
-        getattr = partial(object.__getattribute__, self)
-
-        if name.startswith("_"):
-            return getattr(name)
-        if name == "bake":
-            return getattr("bake")
-        if name.endswith("_"):
-            name = name[:-1]
-
-        return getattr("bake")(name)
-
-
-    @staticmethod
-    def _extract_call_args(kwargs, to_override={}):
-        kwargs = kwargs.copy()
-        call_args = {}
-        for parg, default in Command._call_args.items():
-            key = "_" + parg
-
-            if key in kwargs:
-                call_args[parg] = kwargs[key]
-                del kwargs[key]
-            elif parg in to_override:
-                call_args[parg] = to_override[parg]
-
-        # test for incompatible call args
-        s1 = set(call_args.keys())
-        for args in Command._incompatible_call_args:
-            args = list(args)
-            error = args.pop()
-
-            if s1.issuperset(args):
-                raise TypeError("Invalid special arguments %r: %s" % (args, error))
-
-        return call_args, kwargs
-
-
-    def _aggregate_keywords(self, keywords, sep, raw=False):
-        processed = []
-        for k, v in keywords.items():
-            # we're passing a short arg as a kwarg, example:
-            # cut(d="\t")
-            if len(k) == 1:
-                if v is not False:
-                    processed.append(encode_to_py3bytes_or_py2str("-" + k))
-                    if v is not True:
-                        processed.append(encode_to_py3bytes_or_py2str(v))
-
-            # we're doing a long arg
-            else:
-                if not raw:
-                    k = k.replace("_", "-")
-
-                if v is True:
-                    processed.append(encode_to_py3bytes_or_py2str("--" + k))
-                elif v is False:
-                    pass
-                else:
-                    arg = encode_to_py3bytes_or_py2str("--%s%s%s" % (k, sep, v))
-                    processed.append(arg)
-        return processed
-
-
-    def _compile_args(self, args, kwargs, sep):
-        processed_args = []
-
-        # aggregate positional args
-        for arg in args:
-            if isinstance(arg, (list, tuple)):
-                if not arg:
-                    warnings.warn("Empty list passed as an argument to %r. \
-If you're using glob.glob(), please use sh.glob() instead." % self._path, stacklevel=3)
-                for sub_arg in arg:
-                    processed_args.append(encode_to_py3bytes_or_py2str(sub_arg))
-            elif isinstance(arg, dict):
-                processed_args += self._aggregate_keywords(arg, sep, raw=True)
-            else:
-                processed_args.append(encode_to_py3bytes_or_py2str(arg))
-
-        # aggregate the keyword arguments
-        processed_args += self._aggregate_keywords(kwargs, sep)
-
-        return processed_args
-
-
-    # TODO needs documentation
-    def bake(self, *args, **kwargs):
-        fn = Command(self._path)
-        fn._partial = True
-
-        call_args, kwargs = self._extract_call_args(kwargs)
-
-        pruned_call_args = call_args
-        for k, v in Command._call_args.items():
-            try:
-                if pruned_call_args[k] == v:
-                    del pruned_call_args[k]
-            except KeyError:
-                continue
-
-        fn._partial_call_args.update(self._partial_call_args)
-        fn._partial_call_args.update(pruned_call_args)
-        fn._partial_baked_args.extend(self._partial_baked_args)
-        sep = pruned_call_args.get("long_sep", self._call_args["long_sep"])
-        fn._partial_baked_args.extend(self._compile_args(args, kwargs, sep))
-        return fn
-
-    def __str__(self):
-        """ in python3, should return unicode.  in python2, should return a
-        string of bytes """
-        if IS_PY3:
-            return self.__unicode__()
-        else:
-            return self.__unicode__().encode(DEFAULT_ENCODING)
-
-
-    def __eq__(self, other):
-        try:
-            return str(self) == str(other)
-        except:
-            return False
-    __hash__ = None  # Avoid DeprecationWarning in Python < 3
-
-
-    def __repr__(self):
-        """ in python3, should return unicode.  in python2, should return a
-        string of bytes """
-        return "<Command %r>" % str(self)
-
-
-    def __unicode__(self):
-        """ a magic method defined for python2.  calling unicode() on a
-        self will call this """
-        baked_args = " ".join(item.decode(DEFAULT_ENCODING) for item in self._partial_baked_args)
-        if baked_args:
-            baked_args = " " + baked_args
-        return self._path.decode(DEFAULT_ENCODING) + baked_args
-
-    def __enter__(self):
-        with_context_warning()
-        self(_with=True)
-
-    def __exit__(self, typ, value, traceback):
-        Command._prepend_stack.pop()
-
-
-    def __call__(self, *args, **kwargs):
-        kwargs = kwargs.copy()
-        args = list(args)
-
-        cmd = []
-
-        # aggregate any 'with' contexts
-        call_args = Command._call_args.copy()
-        for prepend in self._prepend_stack:
-            # don't pass the 'with' call arg
-            pcall_args = prepend.call_args.copy()
-            try:
-                del pcall_args["with"]
-            except:
-                pass
-
-            call_args.update(pcall_args)
-            cmd.extend(prepend.cmd)
-
-        cmd.append(self._path)
-
-        # here we extract the special kwargs and override any
-        # special kwargs from the possibly baked command
-        tmp_call_args, kwargs = self._extract_call_args(kwargs, self._partial_call_args)
-        call_args.update(tmp_call_args)
-
-        if not getattr(call_args["ok_code"], "__iter__", None):
-            call_args["ok_code"] = [call_args["ok_code"]]
-
-
-        if call_args["done"]:
-            call_args["bg"] = True
-
-        # check if we're piping via composition
-        stdin = call_args["in"]
-        if args:
-            first_arg = args.pop(0)
-            if isinstance(first_arg, RunningCommand):
-                # it makes sense that if the input pipe of a command is running
-                # in the background, then this command should run in the
-                # background as well
-                if first_arg.call_args["bg"]:
-                    call_args["bg"] = True
-
-                if first_arg.call_args["piped"] == "direct":
-                    stdin = first_arg.process
-                else:
-                    stdin = first_arg.process._pipe_queue
-
-            else:
-                args.insert(0, first_arg)
-
-        processed_args = self._compile_args(args, kwargs, call_args["long_sep"])
-
-        # makes sure our arguments are broken up correctly
-        split_args = self._partial_baked_args + processed_args
-
-        final_args = split_args
-
-        cmd.extend(final_args)
-
-
-        # stdout redirection
-        stdout = call_args["out"]
-        if output_redirect_is_filename(stdout):
-            stdout = open(str(stdout), "wb")
-
-        # stderr redirection
-        stderr = call_args["err"]
-        if output_redirect_is_filename(stderr):
-            stderr = open(str(stderr), "wb")
-
-
-        return RunningCommand(cmd, call_args, stdin, stdout, stderr)
-
-
-
-
-def _start_daemon_thread(fn, *args):
-    thrd = threading.Thread(target=fn, args=args)
-    thrd.daemon = True
-    thrd.start()
-    return thrd
-
-
-def setwinsize(fd, rows_cols):
-    """ set the terminal size of a tty file descriptor.  borrowed logic
-    from pexpect.py """
-    rows, cols = rows_cols
-    TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561)
-
-    s = struct.pack('HHHH', rows, cols, 0, 0)
-    fcntl.ioctl(fd, TIOCSWINSZ, s)
-
-def construct_streamreader_callback(process, handler):
-    """ here we're constructing a closure for our streamreader callback.  this
-    is used in the case that we pass a callback into _out or _err, meaning we
-    want to our callback to handle each bit of output
-
-    we construct the closure based on how many arguments it takes.  the reason
-    for this is to make it as easy as possible for people to use, without
-    limiting them.  a new user will assume the callback takes 1 argument (the
-    data).  as they get more advanced, they may want to terminate the process,
-    or pass some stdin back, and will realize that they can pass a callback of
-    more args """
-
-
-    # implied arg refers to the "self" that methods will pass in.  we need to
-    # account for this implied arg when figuring out what function the user
-    # passed in based on number of args
-    implied_arg = 0
-
-    partial_args = 0
-    handler_to_inspect = handler
-
-    if isinstance(handler, partial):
-        partial_args = len(handler.args)
-        handler_to_inspect = handler.func
-
-    if inspect.ismethod(handler_to_inspect):
-        implied_arg = 1
-        num_args = len(inspect.getargspec(handler_to_inspect).args)
-
-    else:
-        if inspect.isfunction(handler_to_inspect):
-            num_args = len(inspect.getargspec(handler_to_inspect).args)
-
-        # is an object instance with __call__ method
-        else:
-            implied_arg = 1
-            num_args = len(inspect.getargspec(handler_to_inspect.__call__).args)
-
-
-    net_args = num_args - implied_arg - partial_args
-
-    handler_args = ()
-
-    # just the chunk
-    if net_args == 1:
-        handler_args = ()
-
-    # chunk, stdin
-    if net_args == 2:
-        handler_args = (process.stdin,)
-
-    # chunk, stdin, process
-    elif net_args == 3:
-        # notice we're only storing a weakref, to prevent cyclic references
-        # (where the process holds a streamreader, and a streamreader holds a
-        # handler-closure with a reference to the process
-        handler_args = (process.stdin, weakref.ref(process))
-
-    def fn(chunk):
-        # this is pretty ugly, but we're evaluating the process at call-time,
-        # because it's a weakref
-        args = handler_args
-        if len(args) == 2:
-            args = (handler_args[0], handler_args[1]())
-        return handler(chunk, *args)
-
-    return fn
-
-
-
-def handle_process_exit_code(exit_code):
-    """ this should only ever be called once for each child process """
-    # if we exited from a signal, let our exit code reflect that
-    if os.WIFSIGNALED(exit_code):
-        return -os.WTERMSIG(exit_code)
-    # otherwise just give us a normal exit code
-    elif os.WIFEXITED(exit_code):
-        return os.WEXITSTATUS(exit_code)
-    else:
-        raise RuntimeError("Unknown child exit status!")
-
-
-
-
-class OProc(object):
-    """ this class is instantiated by RunningCommand for a command to be exec'd.
-    it handles all the nasty business involved with correctly setting up the
-    input/output to the child process.  it gets its name for subprocess.Popen
-    (process open) but we're calling ours OProc (open process) """
-
-    _default_window_size = (24, 80)
-
-    # used in redirecting
-    STDOUT = -1
-    STDERR = -2
-
-    def __init__(self, parent_log, cmd, stdin, stdout, stderr, call_args, pipe):
-        """
-            cmd is the full string that will be exec'd.  it includes the program
-            name and all its arguments
-
-            stdin, stdout, stderr are what the child will use for standard
-            input/output/err
-
-            call_args is a mapping of all the special keyword arguments to apply
-            to the child process
-        """
-
-        self.call_args = call_args
-
-        # I had issues with getting 'Input/Output error reading stdin' from dd,
-        # until I set _tty_out=False
-        if self.call_args["piped"] == "direct":
-            self.call_args["tty_out"] = False
-
-        self._single_tty = self.call_args["tty_in"] and self.call_args["tty_out"]
-
-        # this logic is a little convoluted, but basically this top-level
-        # if/else is for consolidating input and output TTYs into a single
-        # TTY.  this is the only way some secure programs like ssh will
-        # output correctly (is if stdout and stdin are both the same TTY)
-        if self._single_tty:
-            self._stdin_fd, self._slave_stdin_fd = pty.openpty()
-
-            self._stdout_fd = self._stdin_fd
-            self._slave_stdout_fd = self._slave_stdin_fd
-
-            self._stderr_fd = self._stdin_fd
-            self._slave_stderr_fd = self._slave_stdin_fd
-
-        # do not consolidate stdin and stdout.  this is the most common use-
-        # case
-        else:
-            # this check here is because we may be doing "direct" piping
-            # (_piped="direct"), and so our stdin might be an instance of
-            # OProc
-            if isinstance(stdin, OProc):
-                self._slave_stdin_fd = stdin._stdout_fd
-                self._stdin_fd = None
-            elif self.call_args["tty_in"]:
-                self._slave_stdin_fd, self._stdin_fd = pty.openpty()
-            # tty_in=False is the default
-            else:
-                self._slave_stdin_fd, self._stdin_fd = os.pipe()
-
-
-            # tty_out=True is the default
-            if self.call_args["tty_out"]:
-                self._stdout_fd, self._slave_stdout_fd = pty.openpty()
-            else:
-                self._stdout_fd, self._slave_stdout_fd = os.pipe()
-
-            # unless STDERR is going to STDOUT, it ALWAYS needs to be a pipe,
-            # and never a PTY.  the reason for this is not totally clear to me,
-            # but it has to do with the fact that if STDERR isn't set as the
-            # CTTY (because STDOUT is), the STDERR buffer won't always flush
-            # by the time the process exits, and the data will be lost.
-            # i've only seen this on OSX.
-            if stderr is not OProc.STDOUT:
-                self._stderr_fd, self._slave_stderr_fd = os.pipe()
-
-
-        # this is a hack, but what we're doing here is intentionally throwing an
-        # OSError exception if our child processes's directory doesn't exist,
-        # but we're doing it BEFORE we fork.  the reason for before the fork is
-        # error handling.  i'm currently too lazy to implement what
-        # subprocess.py did and set up a error pipe to handle exceptions that
-        # happen in the child between fork and exec.  it has only been seen in
-        # the wild for a missing cwd, so we'll handle it here.
-        cwd = self.call_args["cwd"]
-        if cwd is not None and not os.path.exists(cwd):
-            os.chdir(cwd)
-
-
-        gc_enabled = gc.isenabled()
-        if gc_enabled:
-            gc.disable()
-        self.pid = os.fork()
-
-
-        # child
-        if self.pid == 0: # pragma: no cover
-            try:
-                # ignoring SIGHUP lets us persist even after the parent process
-                # exits.  only ignore if we're backgrounded
-                if self.call_args["bg"] is True:
-                    signal.signal(signal.SIGHUP, signal.SIG_IGN)
-
-                # this piece of ugliness is due to a bug where we can lose output
-                # if we do os.close(self._slave_stdout_fd) in the parent after
-                # the child starts writing.
-                # see http://bugs.python.org/issue15898
-                if IS_OSX:
-                    time.sleep(0.01)
-
-                os.setsid()
-
-                if self.call_args["tty_out"]:
-                    # set raw mode, so there isn't any weird translation of
-                    # newlines to \r\n and other oddities.  we're not outputting
-                    # to a terminal anyways
-                    #
-                    # we HAVE to do this here, and not in the parent process,
-                    # because we have to guarantee that this is set before the
-                    # child process is run, and we can't do it twice.
-                    tty.setraw(self._slave_stdout_fd)
-
-
-                # if the parent-side fd for stdin exists, close it.  the case
-                # where it may not exist is if we're using piped="direct"
-                if self._stdin_fd:
-                    os.close(self._stdin_fd)
-
-                if not self._single_tty:
-                    os.close(self._stdout_fd)
-                    if stderr is not OProc.STDOUT:
-                        os.close(self._stderr_fd)
-
-
-                if cwd:
-                    os.chdir(cwd)
-
-                os.dup2(self._slave_stdin_fd, 0)
-                os.dup2(self._slave_stdout_fd, 1)
-
-                # we're not directing stderr to stdout?  then set self._slave_stderr_fd to
-                # fd 2, the common stderr fd
-                if stderr is OProc.STDOUT:
-                    os.dup2(self._slave_stdout_fd, 2)
-                else:
-                    os.dup2(self._slave_stderr_fd, 2)
-
-                # don't inherit file descriptors
-                max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
-                os.closerange(3, max_fd)
-
-
-                # set our controlling terminal.  tty_out defaults to true
-                if self.call_args["tty_out"]:
-                    tmp_fd = os.open(os.ttyname(1), os.O_RDWR)
-                    os.close(tmp_fd)
-
-
-                if self.call_args["tty_out"]:
-                    setwinsize(1, self.call_args["tty_size"])
-
-                # actually execute the process
-                if self.call_args["env"] is None:
-                    os.execv(cmd[0], cmd)
-                else:
-                    os.execve(cmd[0], cmd, self.call_args["env"])
-
-            # we must ensure that we ALWAYS exit the child process, otherwise
-            # the parent process code will be executed twice on exception
-            # https://github.com/amoffat/sh/issues/202
-            #
-            # if your parent process experiences an exit code 255, it is most
-            # likely that an exception occurred between the fork of the child
-            # and the exec.  this should be reported.
-            finally:
-                os._exit(255)
-
-        # parent
-        else:
-            if gc_enabled:
-                gc.enable()
-
-            # used to determine what exception to raise.  if our process was
-            # killed via a timeout counter, we'll raise something different than
-            # a SIGKILL exception
-            self.timed_out = False
-
-            self.started = time.time()
-            self.cmd = cmd
-
-            # exit code should only be manipulated from within self._wait_lock
-            # to prevent race conditions
-            self.exit_code = None
-
-            self.stdin = stdin or Queue()
-
-            # _pipe_queue is used internally to hand off stdout from one process
-            # to another.  by default, all stdout from a process gets dumped
-            # into this pipe queue, to be consumed in real time (hence the
-            # thread-safe Queue), or at a potentially later time
-            self._pipe_queue = Queue()
-
-            # this is used to prevent a race condition when we're waiting for
-            # a process to end, and the OProc's internal threads are also checking
-            # for the processes's end
-            self._wait_lock = threading.Lock()
-
-            # these are for aggregating the stdout and stderr.  we use a deque
-            # because we don't want to overflow
-            self._stdout = deque(maxlen=self.call_args["internal_bufsize"])
-            self._stderr = deque(maxlen=self.call_args["internal_bufsize"])
-
-            if self.call_args["tty_in"]:
-                setwinsize(self._stdin_fd, self.call_args["tty_size"])
-
-
-            self.log = parent_log.get_child("process", repr(self))
-
-            os.close(self._slave_stdin_fd)
-            if not self._single_tty:
-                os.close(self._slave_stdout_fd)
-                if stderr is not OProc.STDOUT:
-                    os.close(self._slave_stderr_fd)
-
-            self.log.debug("started process")
-
-
-            if self.call_args["tty_in"]:
-                attr = termios.tcgetattr(self._stdin_fd)
-                attr[3] &= ~termios.ECHO
-                termios.tcsetattr(self._stdin_fd, termios.TCSANOW, attr)
-
-            # this represents the connection from a Queue object (or whatever
-            # we're using to feed STDIN) to the process's STDIN fd
-            self._stdin_stream = None
-            if not isinstance(self.stdin, OProc):
-                self._stdin_stream = \
-                        StreamWriter(self.log.get_child("streamwriter",
-                            "stdin"), self._stdin_fd, self.stdin,
-                            self.call_args["in_bufsize"],
-                            self.call_args["encoding"],
-                            self.call_args["tty_in"])
-
-            stdout_pipe = None
-            if pipe is OProc.STDOUT and not self.call_args["no_pipe"]:
-                stdout_pipe = self._pipe_queue
-
-
-            # this represents the connection from a process's STDOUT fd to
-            # wherever it has to go, sometimes a pipe Queue (that we will use
-            # to pipe data to other processes), and also an internal deque
-            # that we use to aggregate all the output
-            save_stdout = not self.call_args["no_out"] and \
-                (self.call_args["tee"] in (True, "out") or stdout is None)
-
-
-            # if we're piping directly into another process's filedescriptor, we
-            # bypass reading from the stdout stream altogether, because we've
-            # already hooked up this processes's stdout fd to the other
-            # processes's stdin fd
-            self._stdout_stream = None
-            if self.call_args["piped"] != "direct":
-                if callable(stdout):
-                    stdout = construct_streamreader_callback(self, stdout)
-                self._stdout_stream = \
-                        StreamReader(self.log.get_child("streamreader",
-                            "stdout"), self._stdout_fd, stdout, self._stdout,
-                            self.call_args["out_bufsize"],
-                            self.call_args["encoding"],
-                            self.call_args["decode_errors"], stdout_pipe,
-                            save_data=save_stdout)
-
-            if stderr is OProc.STDOUT or self._single_tty:
-                self._stderr_stream = None
-            else:
-                stderr_pipe = None
-                if pipe is OProc.STDERR and not self.call_args["no_pipe"]:
-                    stderr_pipe = self._pipe_queue
-
-                save_stderr = not self.call_args["no_err"] and \
-                    (self.call_args["tee"] in ("err",) or stderr is None)
-
-                if callable(stderr):
-                    stderr = construct_streamreader_callback(self, stderr)
-
-                self._stderr_stream = StreamReader(Logger("streamreader"),
-                    self._stderr_fd, stderr, self._stderr,
-                    self.call_args["err_bufsize"], self.call_args["encoding"],
-                    self.call_args["decode_errors"], stderr_pipe,
-                    save_data=save_stderr)
-
-
-            # start the main io threads
-            # stdin thread is not needed if we are connecting from another process's stdout pipe
-            self._input_thread = None
-            if self._stdin_stream:
-                self._input_thread = _start_daemon_thread(self.input_thread,
-                        self._stdin_stream)
-
-            self._output_thread = _start_daemon_thread(self.output_thread,
-                    self._stdout_stream, self._stderr_stream,
-                    self.call_args["timeout"], self.started,
-                    self.call_args["timeout_signal"])
-
-
-    def __repr__(self):
-        return "<Process %d %r>" % (self.pid, self.cmd[:500])
-
-
-    def change_in_bufsize(self, buf):
-        self._stdin_stream.stream_bufferer.change_buffering(buf)
-
-    def change_out_bufsize(self, buf):
-        self._stdout_stream.stream_bufferer.change_buffering(buf)
-
-    def change_err_bufsize(self, buf):
-        self._stderr_stream.stream_bufferer.change_buffering(buf)
-
-
-    def input_thread(self, stdin):
-        """ this is run in a separate thread.  it writes into our process's
-        stdin (a streamwriter) and waits the process to end AND everything that
-        can be written to be written """
-        done = False
-        while not done and self.is_alive():
-            self.log.debug("%r ready for more input", stdin)
-            done = stdin.write()
-
-        stdin.close()
-
-
-    def output_thread(self, stdout, stderr, timeout, started, timeout_exc):
-        """ this function is run in a separate thread.  it reads from the
-        process's stdout stream (a streamreader), and waits for it to claim that
-        its done """
-
-        readers = []
-        errors = []
-
-        if stdout is not None:
-            readers.append(stdout)
-            errors.append(stdout)
-        if stderr is not None:
-            readers.append(stderr)
-            errors.append(stderr)
-
-        # this is our select loop for polling stdout or stderr that is ready to
-        # be read and processed.  if one of those streamreaders indicate that it
-        # is done altogether being read from, we remove it from our list of
-        # things to poll.  when no more things are left to poll, we leave this
-        # loop and clean up
-        while readers:
-            outputs, inputs, err = select.select(readers, [], errors, 0.1)
-
-            # stdout and stderr
-            for stream in outputs:
-                self.log.debug("%r ready to be read from", stream)
-                done = stream.read()
-                if done:
-                    readers.remove(stream)
-
-            for stream in err:
-                pass
-
-            # test if the process has been running too long
-            if timeout:
-                now = time.time()
-                if now - started > timeout:
-                    self.log.debug("we've been running too long")
-                    self.timed_out = True
-                    self.signal(timeout_exc)
-
-
-        # this is here because stdout may be the controlling TTY, and
-        # we can't close it until the process has ended, otherwise the
-        # child will get SIGHUP.  typically, if we've broken out of
-        # the above loop, and we're here, the process is just about to
-        # end, so it's probably ok to aggressively poll self.is_alive()
-        #
-        # the other option to this would be to do the CTTY close from
-        # the method that does the actual os.waitpid() call, but the
-        # problem with that is that the above loop might still be
-        # running, and closing the fd will cause some operation to
-        # fail.  this is less complex than wrapping all the ops
-        # in the above loop with out-of-band fd-close exceptions
-        while self.is_alive():
-            time.sleep(0.001)
-
-        if stdout:
-            stdout.close()
-
-        if stderr:
-            stderr.close()
-
-
-    @property
-    def stdout(self):
-        return "".encode(self.call_args["encoding"]).join(self._stdout)
-
-    @property
-    def stderr(self):
-        return "".encode(self.call_args["encoding"]).join(self._stderr)
-
-
-    def signal(self, sig):
-        self.log.debug("sending signal %d", sig)
-        try:
-            os.kill(self.pid, sig)
-        except OSError:
-            pass
-
-    def kill(self):
-        self.log.debug("killing")
-        self.signal(signal.SIGKILL)
-
-    def terminate(self):
-        self.log.debug("terminating")
-        self.signal(signal.SIGTERM)
-
-
-    def is_alive(self):
-        """ polls if our child process has completed, without blocking.  this
-        method has side-effects, such as setting our exit_code, if we happen to
-        see our child exit while this is running """
-
-        if self.exit_code is not None:
-            return False
-
-        # what we're doing here essentially is making sure that the main thread
-        # (or another thread), isn't calling .wait() on the process.  because
-        # .wait() calls os.waitpid(self.pid, 0), we can't do an os.waitpid
-        # here...because if we did, and the process exited while in this
-        # thread, the main thread's os.waitpid(self.pid, 0) would raise OSError
-        # (because the process ended in another thread).
-        #
-        # so essentially what we're doing is, using this lock, checking if
-        # we're calling .wait(), and if we are, let .wait() get the exit code
-        # and handle the status, otherwise let us do it.
-        acquired = self._wait_lock.acquire(False)
-        if not acquired:
-            if self.exit_code is not None:
-                return False
-            return True
-
-        try:
-            # WNOHANG is just that...we're calling waitpid without hanging...
-            # essentially polling the process.  the return result is (0, 0) if
-            # there's no process status, so we check that pid == self.pid below
-            # in order to determine how to proceed
-            pid, exit_code = os.waitpid(self.pid, os.WNOHANG)
-            if pid == self.pid:
-                self.exit_code = handle_process_exit_code(exit_code)
-                return False
-
-        # no child process
-        except OSError:
-            return False
-        else:
-            return True
-        finally:
-            self._wait_lock.release()
-
-
-    def wait(self):
-        """ waits for the process to complete, handles the exit code """
-
-        self.log.debug("acquiring wait lock to wait for completion")
-        # using the lock in a with-context blocks, which is what we want if
-        # we're running wait()
-        with self._wait_lock:
-            self.log.debug("got wait lock")
-
-            if self.exit_code is None:
-                self.log.debug("exit code not set, waiting on pid")
-                pid, exit_code = os.waitpid(self.pid, 0) # blocks
-                self.exit_code = handle_process_exit_code(exit_code)
-            else:
-                self.log.debug("exit code already set (%d), no need to wait", self.exit_code)
-
-            # we may not have a thread for stdin, if the pipe has been connected
-            # via _piped="direct"
-            if self._input_thread:
-                self._input_thread.join()
-
-            # wait for our stdout and stderr streamreaders to finish reading and
-            # aggregating the process output
-            self._output_thread.join()
-
-            return self.exit_code
-
-
-
-
-class DoneReadingForever(Exception): pass
-class NotYetReadyToRead(Exception): pass
-
-
-def determine_how_to_read_input(input_obj):
-    """ given some kind of input object, return a function that knows how to
-    read chunks of that input object.
-    
-    each reader function should return a chunk and raise a DoneReadingForever
-    exception, or return None, when there's no more data to read
-
-    NOTE: the function returned does not need to care much about the requested
-    buffering type (eg, unbuffered vs newline-buffered).  the StreamBufferer
-    will take care of that.  these functions just need to return a
-    reasonably-sized chunk of data. """
-
-    get_chunk = None
-
-    if isinstance(input_obj, Queue):
-        log_msg = "queue"
-        get_chunk = get_queue_chunk_reader(input_obj)
-
-    elif callable(input_obj):
-        log_msg = "callable"
-        get_chunk = get_callable_chunk_reader(input_obj)
-
-    # also handles stringio
-    elif hasattr(input_obj, "read"):
-        log_msg = "file descriptor"
-        get_chunk = get_file_chunk_reader(input_obj)
-
-    elif isinstance(input_obj, basestring):
-        log_msg = "string"
-        get_chunk = get_iter_string_reader(input_obj)
-
-    else:
-        log_msg = "general iterable"
-        get_chunk = get_iter_chunk_reader(iter(input_obj))
-
-    return get_chunk, log_msg
-
-
-
-def get_queue_chunk_reader(stdin):
-    def fn():
-        try:
-            chunk = stdin.get(True, 0.01)
-        except Empty:
-            raise NotYetReadyToRead
-        if chunk is None:
-            raise DoneReadingForever
-        return chunk
-    return fn
-
-
-def get_callable_chunk_reader(stdin):
-    def fn():
-        try:
-            return stdin()
-        except:
-            raise DoneReadingForever
-    return fn
-
-
-def get_iter_string_reader(stdin):
-    """ return an iterator that returns a chunk of a string every time it is
-    called.  notice that even though bufsize_type might be line buffered, we're
-    not doing any line buffering here.  that's because our StreamBufferer
-    handles all buffering.  we just need to return a reasonable-sized chunk. """
-    bufsize = 1024
-    iter_str = (stdin[i:i + bufsize] for i in range(0, len(stdin), bufsize))
-    return get_iter_chunk_reader(iter_str)
-
-
-def get_iter_chunk_reader(stdin):
-    def fn():
-        try:
-            if IS_PY3:
-                chunk = stdin.__next__()
-            else:
-                chunk = stdin.next()
-            return chunk
-        except StopIteration:
-            raise DoneReadingForever
-    return fn
-
-def get_file_chunk_reader(stdin):
-    bufsize = 1024
-
-    def fn():
-        chunk = stdin.read(bufsize)
-        if not chunk:
-            raise DoneReadingForever
-        else:
-            return chunk
-    return fn
-
-
-def bufsize_type_to_bufsize(bf_type):
-    """ for a given bufsize type, return the actual bufsize we will read.
-    notice that although 1 means "newline-buffered", we're reading a chunk size
-    of 1024.  this is because we have to read something.  we let a
-    StreamBufferer instance handle splitting our chunk on newlines """
-
-    # newlines
-    if bf_type == 1:
-        bufsize = 1024
-    # unbuffered
-    elif bf_type == 0:
-        bufsize = 1
-    # or buffered by specific amount
-    else:
-        bufsize = bf_type
-
-    return bufsize
-
-
-
-class StreamWriter(object):
-    """ StreamWriter reads from some input (the stdin param) and writes to a fd
-    (the stream param).  the stdin may be a Queue, a callable, something with
-    the "read" method, a string, or an iterable """
-
-    def __init__(self, log, stream, stdin, bufsize_type, encoding, tty_in):
-        self.stream = stream
-        self.stdin = stdin
-
-        self.log = log
-        self.encoding = encoding
-        self.tty_in = tty_in
-
-
-        self.stream_bufferer = StreamBufferer(bufsize_type, self.encoding)
-        self.get_chunk, log_msg = determine_how_to_read_input(stdin)
-        self.log.debug("parsed stdin as a %s", log_msg)
-
-
-    def fileno(self):
-        """ defining this allows us to do select.select on an instance of this
-        class """
-        return self.stream
-
-
-
-    def write(self):
-        """ attempt to get a chunk of data to write to our child process's
-        stdin, then write it.  the return value answers the questions "are we
-        done writing forever?" """
-
-        # get_chunk may sometimes return bytes, and sometimes returns trings
-        # because of the nature of the different types of STDIN objects we
-        # support
-        try:
-            chunk = self.get_chunk()
-            if chunk is None:
-                raise DoneReadingForever
-
-        except DoneReadingForever:
-            self.log.debug("done reading")
-
-            if self.tty_in:
-                # EOF time
-                try:
-                    char = termios.tcgetattr(self.stream)[6][termios.VEOF]
-                except:
-                    char = chr(4).encode()
-                os.write(self.stream, char)
-
-            return True
-
-        except NotYetReadyToRead:
-            self.log.debug("received no data")
-            return False
-
-        # if we're not bytes, make us bytes
-        if IS_PY3 and hasattr(chunk, "encode"):
-            chunk = chunk.encode(self.encoding)
-
-        for proc_chunk in self.stream_bufferer.process(chunk):
-            self.log.debug("got chunk size %d: %r", len(proc_chunk),
-                    proc_chunk[:30])
-
-            self.log.debug("writing chunk to process")
-            try:
-                os.write(self.stream, proc_chunk)
-            except OSError:
-                self.log.debug("OSError writing stdin chunk")
-                return True
-
-
-    def close(self):
-        self.log.debug("closing, but flushing first")
-        chunk = self.stream_bufferer.flush()
-        self.log.debug("got chunk size %d to flush: %r", len(chunk), chunk[:30])
-        try:
-            if chunk:
-                os.write(self.stream, chunk)
-
-            if not self.tty_in:
-                self.log.debug("we used a TTY, so closing the stream")
-                os.close(self.stream)
-
-        except OSError:
-            pass
-
-
-
-def determine_how_to_feed_output(handler, encoding, decode_errors):
-    if callable(handler):
-        process, finish = get_callback_chunk_consumer(handler, encoding,
-                decode_errors)
-    elif isinstance(handler, cStringIO):
-        process, finish = get_cstringio_chunk_consumer(handler)
-    elif isinstance(handler, StringIO):
-        process, finish = get_stringio_chunk_consumer(handler, encoding,
-                decode_errors)
-    elif hasattr(handler, "write"):
-        process, finish = get_file_chunk_consumer(handler)
-    else:
-        process = lambda chunk: False
-        finish = lambda: None
-
-    return process, finish
-
-
-def get_file_chunk_consumer(handler):
-    def process(chunk):
-        handler.write(chunk)
-        # we should flush on an fd.  chunk is already the correctly-buffered
-        # size, so we don't need the fd buffering as well
-        handler.flush()
-        return False
-
-    def finish():
-        if hasattr(handler, "flush"):
-            handler.flush()
-
-    return process, finish
-
-def get_callback_chunk_consumer(handler, encoding, decode_errors):
-    def process(chunk):
-        # try to use the encoding first, if that doesn't work, send
-        # the bytes, because it might be binary
-        try:
-            chunk = chunk.decode(encoding, decode_errors)
-        except UnicodeDecodeError:
-            pass
-        return handler(chunk)
-
-    def finish():
-        pass
-
-    return process, finish
-
-def get_cstringio_chunk_consumer(handler):
-    def process(chunk):
-        handler.write(chunk)
-        return False
-
-    def finish():
-        pass
-
-    return process, finish
-
-
-def get_stringio_chunk_consumer(handler, encoding, decode_errors):
-    def process(chunk):
-        handler.write(chunk.decode(encoding, decode_errors))
-        return False
-
-    def finish():
-        pass
-
-    return process, finish
-
-
-class StreamReader(object):
-    """ reads from some output (the stream) and sends what it just read to the
-    handler.  """
-    def __init__(self, log, stream, handler, buffer, bufsize_type, encoding,
-            decode_errors, pipe_queue=None, save_data=True):
-        self.stream = stream
-        self.buffer = buffer
-        self.save_data = save_data
-        self.encoding = encoding
-        self.decode_errors = decode_errors
-
-        self.pipe_queue = None
-        if pipe_queue:
-            self.pipe_queue = weakref.ref(pipe_queue)
-
-        self.log = log
-
-        self.stream_bufferer = StreamBufferer(bufsize_type, self.encoding,
-                self.decode_errors)
-        self.bufsize = bufsize_type_to_bufsize(bufsize_type)
-
-        self.process_chunk, self.finish_chunk_processor = \
-                determine_how_to_feed_output(handler, encoding, decode_errors)
-
-        self.should_quit = False
-
-
-    def fileno(self):
-        """ defining this allows us to do select.select on an instance of this
-        class """
-        return self.stream
-
-    def close(self):
-        chunk = self.stream_bufferer.flush()
-        self.log.debug("got chunk size %d to flush: %r", len(chunk), chunk[:30])
-        if chunk:
-            self.write_chunk(chunk)
-
-        self.finish_chunk_processor()
-
-        if self.pipe_queue and self.save_data:
-            self.pipe_queue().put(None)
-
-        try:
-            os.close(self.stream)
-        except OSError:
-            pass
-
-
-    def write_chunk(self, chunk):
-        # in PY3, the chunk coming in will be bytes, so keep that in mind
-
-        if not self.should_quit:
-            self.should_quit = self.process_chunk(chunk)
-
-
-        if self.save_data:
-            self.buffer.append(chunk)
-
-            if self.pipe_queue:
-                self.log.debug("putting chunk onto pipe: %r", chunk[:30])
-                self.pipe_queue().put(chunk)
-
-
-    def read(self):
-        # if we're PY3, we're reading bytes, otherwise we're reading
-        # str
-        try:
-            chunk = os.read(self.stream, self.bufsize)
-        except OSError as e:
-            self.log.debug("got errno %d, done reading", e.errno)
-            return True
-        if not chunk:
-            self.log.debug("got no chunk, done reading")
-            return True
-
-        self.log.debug("got chunk size %d: %r", len(chunk), chunk[:30])
-        for chunk in self.stream_bufferer.process(chunk):
-            self.write_chunk(chunk)
-
-
-
-
-class StreamBufferer(object):
-    """ this is used for feeding in chunks of stdout/stderr, and breaking it up
-    into chunks that will actually be put into the internal buffers.  for
-    example, if you have two processes, one being piped to the other, and you
-    want that, first process to feed lines of data (instead of the chunks
-    however they come in), OProc will use an instance of this class to chop up
-    the data and feed it as lines to be sent down the pipe """
-
-    def __init__(self, buffer_type, encoding=DEFAULT_ENCODING,
-            decode_errors="strict"):
-        # 0 for unbuffered, 1 for line, everything else for that amount
-        self.type = buffer_type
-        self.buffer = []
-        self.n_buffer_count = 0
-        self.encoding = encoding
-        self.decode_errors = decode_errors
-
-        # this is for if we change buffering types.  if we change from line
-        # buffered to unbuffered, its very possible that our self.buffer list
-        # has data that was being saved up (while we searched for a newline).
-        # we need to use that up, so we don't lose it
-        self._use_up_buffer_first = False
-
-        # the buffering lock is used because we might chance the buffering
-        # types from a different thread.  for example, if we have a stdout
-        # callback, we might use it to change the way stdin buffers.  so we
-        # lock
-        self._buffering_lock = threading.RLock()
-        self.log = Logger("stream_bufferer")
-
-
-    def change_buffering(self, new_type):
-        # TODO, when we stop supporting 2.6, make this a with context
-        self.log.debug("acquiring buffering lock for changing buffering")
-        self._buffering_lock.acquire()
-        self.log.debug("got buffering lock for changing buffering")
-        try:
-            if new_type == 0:
-                self._use_up_buffer_first = True
-
-            self.type = new_type
-        finally:
-            self._buffering_lock.release()
-            self.log.debug("released buffering lock for changing buffering")
-
-
-    def process(self, chunk):
-        # MAKE SURE THAT THE INPUT IS PY3 BYTES
-        # THE OUTPUT IS ALWAYS PY3 BYTES
-
-        # TODO, when we stop supporting 2.6, make this a with context
-        self.log.debug("acquiring buffering lock to process chunk (buffering: %d)", self.type)
-        self._buffering_lock.acquire()
-        self.log.debug("got buffering lock to process chunk (buffering: %d)", self.type)
-        try:
-            # we've encountered binary, permanently switch to N size buffering
-            # since matching on newline doesn't make sense anymore
-            if self.type == 1:
-                try:
-                    chunk.decode(self.encoding, self.decode_errors)
-                except:
-                    self.log.debug("detected binary data, changing buffering")
-                    self.change_buffering(1024)
-
-            # unbuffered
-            if self.type == 0:
-                if self._use_up_buffer_first:
-                    self._use_up_buffer_first = False
-                    to_write = self.buffer
-                    self.buffer = []
-                    to_write.append(chunk)
-                    return to_write
-
-                return [chunk]
-
-            # line buffered
-            # we must decode the bytes before we try to match on newline
-            elif self.type == 1:
-                total_to_write = []
-                chunk = chunk.decode(self.encoding, self.decode_errors)
-                while True:
-                    newline = chunk.find("\n")
-                    if newline == -1:
-                        break
-
-                    chunk_to_write = chunk[:newline + 1]
-                    if self.buffer:
-                        # this is ugly, but it's designed to take the existing
-                        # bytes buffer, join it together, tack on our latest
-                        # chunk, then convert the whole thing to a string.
-                        # it's necessary, i'm sure.  read the whole block to
-                        # see why.
-                        chunk_to_write = "".encode(self.encoding).join(self.buffer) \
-                            + chunk_to_write.encode(self.encoding)
-                        chunk_to_write = chunk_to_write.decode(self.encoding)
-
-                        self.buffer = []
-                        self.n_buffer_count = 0
-
-                    chunk = chunk[newline + 1:]
-                    total_to_write.append(chunk_to_write.encode(self.encoding))
-
-                if chunk:
-                    self.buffer.append(chunk.encode(self.encoding))
-                    self.n_buffer_count += len(chunk)
-                return total_to_write
-
-            # N size buffered
-            else:
-                total_to_write = []
-                while True:
-                    overage = self.n_buffer_count + len(chunk) - self.type
-                    if overage >= 0:
-                        ret = "".encode(self.encoding).join(self.buffer) + chunk
-                        chunk_to_write = ret[:self.type]
-                        chunk = ret[self.type:]
-                        total_to_write.append(chunk_to_write)
-                        self.buffer = []
-                        self.n_buffer_count = 0
-                    else:
-                        self.buffer.append(chunk)
-                        self.n_buffer_count += len(chunk)
-                        break
-                return total_to_write
-        finally:
-            self._buffering_lock.release()
-            self.log.debug("released buffering lock for processing chunk (buffering: %d)", self.type)
-
-
-    def flush(self):
-        self.log.debug("acquiring buffering lock for flushing buffer")
-        self._buffering_lock.acquire()
-        self.log.debug("got buffering lock for flushing buffer")
-        try:
-            ret = "".encode(self.encoding).join(self.buffer)
-            self.buffer = []
-            return ret
-        finally:
-            self._buffering_lock.release()
-            self.log.debug("released buffering lock for flushing buffer")
-
-
-
-@contextmanager
-def pushd(path):
-    """ pushd is just a specialized form of args, where we're passing in the
-    current working directory """
-    with args(_cwd=path):
-        yield
-
-
-@contextmanager
-def args(*args, **kwargs):
-    """ allows us to temporarily override all the special keyword parameters in
-    a with context """
-    call_args = Command._call_args
-    old_args = call_args.copy()
-
-    for key,value in kwargs.items():
-        key = key.lstrip("_")
-        call_args[key] = value
-
-    yield
-    call_args.update(old_args)
-
-
-
-class Environment(dict):
-    """ this allows lookups to names that aren't found in the global scope to be
-    searched for as a program name.  for example, if "ls" isn't found in this
-    module's scope, we consider it a system program and try to find it.
-
-    we use a dict instead of just a regular object as the base class because the
-    exec() statement used in this file requires the "globals" argument to be a
-    dictionary """
-
-
-    # this is a list of all of the names that the sh module exports that will
-    # not resolve to functions.  we don't want to accidentally shadow real
-    # commands with functions/imports that we define in sh.py.  for example,
-    # "import time" may override the time system program
-    whitelist = set([
-        "Command",
-        "CommandNotFound",
-        "DEFAULT_ENCODING",
-        "DoneReadingForever",
-        "ErrorReturnCode",
-        "NotYetReadyToRead",
-        "SignalException",
-        "TimeoutException",
-        "__project_url__",
-        "__version__",
-        "args",
-        "glob",
-        "pushd",
-    ])
-
-    def __init__(self, globs, baked_args={}):
-        self.globs = globs
-        self.baked_args = baked_args
-        self.disable_whitelist = False
-
-    def __setitem__(self, k, v):
-        self.globs[k] = v
-
-    def __getitem__(self, k):
-        # if we first import "_disable_whitelist" from sh, we can import
-        # anything defined in the global scope of sh.py.  this is useful for our
-        # tests
-        if k == "_disable_whitelist":
-            self.disable_whitelist = True
-            return None
-
-        # we're trying to import something real (maybe), see if it's in our
-        # global scope
-        if k in self.whitelist or self.disable_whitelist:
-            try:
-                return self.globs[k]
-            except KeyError:
-                pass
-
-        # somebody tried to be funny and do "from sh import *"
-        if k == "__all__":
-            raise AttributeError("Cannot import * from sh. \
-Please import sh or import programs individually.")
-
-
-        # check if we're naming a dynamically generated ReturnCode exception
-        exc = get_exc_from_name(k)
-        if exc:
-            return exc
-
-
-        # https://github.com/ipython/ipython/issues/2577
-        # https://github.com/amoffat/sh/issues/97#issuecomment-10610629
-        if k.startswith("__") and k.endswith("__"):
-            raise AttributeError
-
-        # how about an environment variable?
-        try:
-            return os.environ[k]
-        except KeyError:
-            pass
-
-        # is it a custom builtin?
-        builtin = getattr(self, "b_" + k, None)
-        if builtin:
-            return builtin
-
-        # it must be a command then
-        # we use _create instead of instantiating the class directly because
-        # _create uses resolve_program, which will automatically do underscore-
-        # to-dash conversions.  instantiating directly does not use that
-        return Command._create(k, **self.baked_args)
-
-
-    # methods that begin with "b_" are custom builtins and will override any
-    # program that exists in our path.  this is useful for things like
-    # common shell builtins that people are used to, but which aren't actually
-    # full-fledged system binaries
-
-    def b_cd(self, path):
-        os.chdir(path)
-
-    def b_which(self, program):
-        return which(program)
-
-
-
-
-def run_repl(env): # pragma: no cover
-    banner = "\n>> sh v{version}\n>> https://github.com/amoffat/sh\n"
-
-    print(banner.format(version=__version__))
-    while True:
-        try:
-            line = raw_input("sh> ")
-        except (ValueError, EOFError):
-            break
-
-        try:
-            exec(compile(line, "<dummy>", "single"), env, env)
-        except SystemExit:
-            break
-        except:
-            print(traceback.format_exc())
-
-    # cleans up our last line
-    print("")
-
-
-
-
-# this is a thin wrapper around THIS module (we patch sys.modules[__name__]).
-# this is in the case that the user does a "from sh import whatever"
-# in other words, they only want to import certain programs, not the whole
-# system PATH worth of commands.  in this case, we just proxy the
-# import lookup to our Environment class
-class SelfWrapper(ModuleType):
-    def __init__(self, self_module, baked_args={}):
-        # this is super ugly to have to copy attributes like this,
-        # but it seems to be the only way to make reload() behave
-        # nicely.  if i make these attributes dynamic lookups in
-        # __getattr__, reload sometimes chokes in weird ways...
-        for attr in ["__builtins__", "__doc__", "__name__", "__package__"]:
-            setattr(self, attr, getattr(self_module, attr, None))
-
-        # python 3.2 (2.7 and 3.3 work fine) breaks on osx (not ubuntu)
-        # if we set this to None.  and 3.3 needs a value for __path__
-        self.__path__ = []
-        self.__self_module = self_module
-        self.__env = Environment(globals(), baked_args)
-
-    def __setattr__(self, name, value):
-        if hasattr(self, "__env"):
-            self.__env[name] = value
-        else:
-            ModuleType.__setattr__(self, name, value)
-
-    def __getattr__(self, name):
-        if name == "__env":
-            raise AttributeError
-        return self.__env[name]
-
-    # accept special keywords argument to define defaults for all operations
-    # that will be processed with given by return SelfWrapper
-    def __call__(self, **kwargs):
-        return SelfWrapper(self.__self_module, kwargs)
-
-
-
-# we're being run as a stand-alone script
-if __name__ == "__main__": # pragma: no cover
-    try:
-        arg = sys.argv.pop(1)
-    except:
-        arg = None
-
-    if arg == "test":
-        import subprocess
-
-        def run_test(version, locale):
-            py_version = "python%s" % version
-            py_bin = which(py_version)
-
-            if py_bin:
-                print("Testing %s, locale %r" % (py_version.capitalize(),
-                    locale))
-
-                env = os.environ.copy()
-                env["LANG"] = locale
-                p = subprocess.Popen([py_bin, os.path.join(THIS_DIR, "test.py")]
-                    + sys.argv[1:], env=env)
-                return_code = p.wait()
-
-                if return_code != 0:
-                    exit(1)
-            else:
-                print("Couldn't find %s, skipping" % py_version.capitalize())
-
-        versions = ("2.6", "2.7", "3.1", "3.2", "3.3", "3.4")
-        locales = ("en_US.UTF-8", "C")
-        for locale in locales:
-            for version in versions:
-                run_test(version, locale)
-
-    else:
-        env = Environment(globals())
-        run_repl(env)
-
-# we're being imported from somewhere
-else:
-    self = sys.modules[__name__]
-    sys.modules[__name__] = SelfWrapper(self)