changeset 0:7d0255678d8b

wtf?
author Jordi Gutiérrez Hermoso <jordigh@octave.org>
date Mon, 06 Oct 2014 15:18:55 -0400
parents
children 0d89b02cb45c
files sh.py sh.pyc ss/bin/scansetup ss/lib/perl5/NeuroRx/ScanSetup.pm ss/lib/perl5/NeuroRx/ScanSetup/IdentifyModality.pm ss/lib/perl5/NeuroRx/ScanSetup/RenameMincFiles.pm wtf
diffstat 7 files changed, 2102 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/sh.py
@@ -0,0 +1,1695 @@
+#===============================================================================
+# Copyright (C) 2011-2012 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.08"
+__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
+from types import ModuleType
+from functools import partial
+import inspect
+import time as _time
+
+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
+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__))
+
+
+import errno
+import warnings
+
+import pty
+import termios
+import signal
+import gc
+import select
+import atexit
+import threading
+import tty
+import fcntl
+import struct
+import resource
+from collections import deque
+import logging
+import weakref
+
+
+logging_enabled = False
+
+
+if IS_PY3:
+    raw_input = input
+    unicode = str
+    basestring = str
+
+
+
+
+class ErrorReturnCode(Exception):
+    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: tstdout = "<redirected>"
+        else:
+            tstdout = self.stdout[:self.truncate_cap] 
+            out_delta = len(self.stdout) - len(tstdout)
+            if out_delta: 
+                tstdout += ("... (%d more, please see e.stdout)" % out_delta).encode()
+            
+        if self.stderr is None: tstderr = "<redirected>"
+        else:
+            tstderr = self.stderr[:self.truncate_cap]
+            err_delta = len(self.stderr) - len(tstderr)
+            if err_delta: 
+                tstderr += ("... (%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, tstdout.decode(DEFAULT_ENCODING), tstderr.decode(DEFAULT_ENCODING))
+        super(ErrorReturnCode, self).__init__(msg)
+        
+        
+class SignalException(ErrorReturnCode): pass
+
+SIGNALS_THAT_SHOULD_THROW_EXCEPTION = (
+    signal.SIGKILL,
+    signal.SIGSEGV,
+    signal.SIGTERM,
+    signal.SIGINT,
+    signal.SIGQUIT
+)
+
+
+# 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+)")
+rc_exc_cache = {}
+
+def get_rc_exc(rc):
+    rc = int(rc)
+    try: return rc_exc_cache[rc]
+    except KeyError: pass
+    
+    if rc > 0:
+        name = "ErrorReturnCode_%d" % rc
+        exc = type(name, (ErrorReturnCode,), {})
+    else:
+        name = "SignalException_%d" % abs(rc)
+        exc = type(name, (SignalException,), {})
+        
+    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)
+
+    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):
+    def __init__(self, name, context=None):
+        self.name = name
+        self.context = "%s"
+        if context: self.context = "%s: %%s" % context
+        self.log = logging.getLogger(name)
+    
+    def info(self, msg, *args):
+        if not logging_enabled: return
+        self.log.info(self.context, msg % args)
+        
+    def debug(self, msg, *args):
+        if not logging_enabled: return
+        self.log.debug(self.context, msg % args)
+        
+    def error(self, msg, *args):
+        if not logging_enabled: return
+        self.log.error(self.context, msg % args)
+        
+    def exception(self, msg, *args):
+        if not logging_enabled: return
+        self.log.exception(self.context, msg % args)
+
+
+
+class RunningCommand(object):
+    def __init__(self, cmd, call_args, stdin, stdout, stderr):
+        truncate = 20
+        if len(cmd) > truncate:
+            logger_str = "command %r...(%d more) call_args %r" % \
+                (cmd[:truncate], len(cmd) - truncate, call_args)
+        else:
+            logger_str = "command %r call_args %r" % (cmd, call_args)
+        
+        self.log = Logger("command", logger_str)
+        self.call_args = call_args
+        self.cmd = cmd
+        self.ran = " ".join(cmd)
+        self.process = None
+        
+        # this flag is for whether or not we've handled the exit code (like
+        # by raising an exception).  this is necessary because .wait() is called
+        # from multiple places, and wait() triggers the exit code to be
+        # processed.  but we don't want to raise multiple exceptions, only
+        # one (if any at all)
+        self._handled_exit_code = False
+
+        self.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 callable(call_args["out"]) or callable(call_args["err"]):
+            self.should_wait = False
+            
+        if call_args["piped"] or call_args["iter"] or call_args["iter_noblock"]:
+            self.should_wait = False
+            
+        # we're running in the background, return self and let us lazily
+        # evaluate
+        if call_args["bg"]: self.should_wait = False
+
+        # redirection
+        if call_args["err_to_out"]: stderr = 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 = STDOUT
+        if call_args["iter"] == "out" or call_args["iter"] is True: pipe = STDOUT
+        elif call_args["iter"] == "err": pipe = STDERR
+        
+        if call_args["iter_noblock"] == "out" or call_args["iter_noblock"] is True: pipe = STDOUT
+        elif call_args["iter_noblock"] == "err": pipe = STDERR
+        
+        
+        if spawn_process:
+            self.log.debug("starting process")
+            self.process = OProc(cmd, stdin, stdout, stderr, 
+                self.call_args, pipe=pipe)
+            
+            if self.should_wait:
+                self.wait()
+        
+
+    def wait(self):
+        self._handle_exit_code(self.process.wait())
+        return self
+    
+    # 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
+    def _handle_exit_code(self, code):
+        if self._handled_exit_code: return
+        self._handled_exit_code = True
+        
+        if code not in self.call_args["ok_code"] and \
+        (code > 0 or -code in SIGNALS_THAT_SHOULD_THROW_EXCEPTION):
+            raise get_rc_exc(code)(
+                " ".join(self.cmd),
+                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.
+        pass
+    
+    def __iter__(self):
+        return self
+    
+    def next(self):
+        # 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(False, .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):
+        if IS_PY3: return self.__unicode__()
+        else: return unicode(self).encode(self.call_args["encoding"])
+        
+    def __unicode__(self):
+        if self.process and self.stdout:
+            return self.stdout.decode(self.call_args["encoding"],
+                self.call_args["decode_errors"])
+        return ""
+
+    def __eq__(self, other):
+        return unicode(self) == unicode(other)
+
+    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
+        return getattr(unicode(self), p)
+     
+    def __repr__(self):
+        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())
+
+
+
+
+
+class Command(object):
+    _prepend_stack = []
+    
+    _call_args = {
+        # currently unsupported
+        #"fg": False, # run command in foreground
+        
+        "bg": False, # run command in background
+        "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,
+        "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,
+        
+        # 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,
+    }
+    
+    # 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"),
+    )
+
+
+    # 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):
+        path = which(path)
+        if not path: raise CommandNotFound(path)
+        self._path = path
+            
+        self._partial = False
+        self._partial_baked_args = []
+        self._partial_call_args = {}
+        
+        # bugfix for functools.wraps.  issue #121
+        self.__name__ = repr(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
+
+
+    # this helper method is for normalizing an argument into a string in the
+    # system's default encoding.  we can feed it a number or a string or
+    # whatever 
+    def _format_arg(self, arg):
+        if IS_PY3: arg = str(arg)
+        else:
+            # if the argument is already unicode, or a number or whatever,
+            # this first call will fail.  
+            try: arg = unicode(arg, DEFAULT_ENCODING).encode(DEFAULT_ENCODING)
+            except TypeError: arg = unicode(arg).encode(DEFAULT_ENCODING)
+        return arg
+
+        
+    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("-" + k)
+                    if v is not True:
+                        processed.append(self._format_arg(v))
+                        
+            # we're doing a long arg
+            else:
+                if not raw:  k = k.replace("_", "-")
+                
+                if v is True:
+                    processed.append("--" + k)
+                elif v is False:
+                    pass
+                else:
+                    processed.append("--%s%s%s" % (k, sep, self._format_arg(v)))
+        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(self._format_arg(sub_arg))
+            elif isinstance(arg, dict):
+                processed_args += self._aggregate_keywords(arg, sep, raw=True)
+            else: 
+                processed_args.append(self._format_arg(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):
+        if IS_PY3: return self.__unicode__()
+        else: return unicode(self).encode(DEFAULT_ENCODING)
+        
+    def __eq__(self, other):
+        try: return str(self) == str(other)
+        except: return False
+
+    def __repr__(self):
+        return "<Command %r>" % str(self)
+        
+    def __unicode__(self):
+        baked_args = " ".join(self._partial_baked_args)
+        if baked_args: baked_args = " " + baked_args
+        return self._path + baked_args
+
+    def __enter__(self):
+        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 isinstance(call_args["ok_code"], (tuple, list)):    
+            call_args["ok_code"] = [call_args["ok_code"]]
+            
+        
+        # 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
+                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 stdout \
+            and not callable(stdout) \
+            and not hasattr(stdout, "write") \
+            and not isinstance(stdout, (cStringIO, StringIO)):
+            
+            stdout = open(str(stdout), "wb")
+        
+
+        # stderr redirection
+        stderr = call_args["err"]
+        if stderr and not callable(stderr) and not hasattr(stderr, "write") \
+            and not isinstance(stderr, (cStringIO, StringIO)):
+            stderr = open(str(stderr), "wb")
+            
+
+        return RunningCommand(cmd, call_args, stdin, stdout, stderr)
+
+
+
+
+# used in redirecting
+STDOUT = -1
+STDERR = -2
+
+
+
+# Process open = Popen
+# Open Process = OProc
+class OProc(object):
+    _procs_to_cleanup = set()
+    _registered_cleanup = False
+    _default_window_size = (24, 80)
+
+    def __init__(self, cmd, stdin, stdout, stderr, call_args,
+            persist=False, pipe=STDOUT):
+
+        self.call_args = call_args
+
+        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
+        else:
+            if self.call_args["tty_in"]:
+                self._slave_stdin_fd, self._stdin_fd = pty.openpty()
+            else:
+                self._slave_stdin_fd, self._stdin_fd = os.pipe()
+            
+            # tty_out is usually 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 STDOUT:
+                self._stderr_fd, self._slave_stderr_fd = os.pipe()
+            
+        gc_enabled = gc.isenabled()
+        if gc_enabled: gc.disable()
+        self.pid = os.fork()
+
+
+        # child
+        if self.pid == 0:
+            # 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 and IS_PY3: _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 thread, 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._stdout_fd)
+                
+                
+            os.close(self._stdin_fd)
+            if not self._single_tty:
+                os.close(self._stdout_fd)
+                if stderr is not STDOUT: os.close(self._stderr_fd)                
+                    
+                    
+            if self.call_args["cwd"]: os.chdir(self.call_args["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 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
+            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"]:
+                self.setwinsize(1)
+            
+            # 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"])
+
+            os._exit(255)
+
+        # parent
+        else:
+            if gc_enabled: gc.enable()
+            
+            if not OProc._registered_cleanup:
+                atexit.register(OProc._cleanup_procs)
+                OProc._registered_cleanup = True
+        
+        
+            self.started = _time.time()
+            self.cmd = cmd
+            self.exit_code = None
+            
+            self.stdin = stdin or Queue()
+            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"]: self.setwinsize(self._stdin_fd)
+            
+            
+            self.log = Logger("process", repr(self))
+            
+            os.close(self._slave_stdin_fd)
+            if not self._single_tty:
+                os.close(self._slave_stdout_fd)
+                if stderr is not STDOUT: os.close(self._slave_stderr_fd)
+            
+            self.log.debug("started process")
+            if not persist: OProc._procs_to_cleanup.add(self)
+
+
+            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 = StreamWriter("stdin", self, self._stdin_fd,
+                self.stdin, self.call_args["in_bufsize"])
+                           
+                        
+            stdout_pipe = None   
+            if pipe is 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)
+            self._stdout_stream = StreamReader("stdout", self, self._stdout_fd, stdout,
+                self._stdout, self.call_args["out_bufsize"], stdout_pipe,
+                save_data=save_stdout)
+                
+                
+            if stderr is STDOUT or self._single_tty: self._stderr_stream = None 
+            else:
+                stderr_pipe = None
+                if pipe is 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)
+                self._stderr_stream = StreamReader("stderr", self, self._stderr_fd, stderr,
+                    self._stderr, self.call_args["err_bufsize"], stderr_pipe,
+                    save_data=save_stderr)
+            
+            # start the main io threads
+            self._input_thread = self._start_thread(self.input_thread, self._stdin_stream)
+            self._output_thread = self._start_thread(self.output_thread, self._stdout_stream, self._stderr_stream)
+            
+            
+    def __repr__(self):
+        return "<Process %d %r>" % (self.pid, self.cmd[:500])        
+            
+
+    # also borrowed from pexpect.py
+    @staticmethod
+    def setwinsize(fd):
+        rows, cols = OProc._default_window_size
+        TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561)
+        if TIOCSWINSZ == 2148037735: # L is not required in Python >= 2.2.
+            TIOCSWINSZ = -2146929561 # Same bits, but with sign.
+
+        s = struct.pack('HHHH', rows, cols, 0, 0)
+        fcntl.ioctl(fd, TIOCSWINSZ, s)
+
+
+    @staticmethod
+    def _start_thread(fn, *args):
+        thrd = threading.Thread(target=fn, args=args)
+        thrd.daemon = True
+        thrd.start()
+        return thrd
+    
+    def in_bufsize(self, buf):
+        self._stdin_stream.stream_bufferer.change_buffering(buf)
+                
+    def out_bufsize(self, buf):
+        self._stdout_stream.stream_bufferer.change_buffering(buf)
+    
+    def err_bufsize(self, buf):
+        if self._stderr_stream:
+            self._stderr_stream.stream_bufferer.change_buffering(buf)
+
+
+    def input_thread(self, stdin):
+        done = False
+        while not done and self.alive:
+            self.log.debug("%r ready for more input", stdin)
+            done = stdin.write()
+
+        stdin.close()
+            
+            
+    def output_thread(self, stdout, stderr):
+        readers = []
+        errors = []
+
+        if stdout is not None:
+            readers.append(stdout)
+            errors.append(stdout)
+        if stderr is not None:
+            readers.append(stderr)
+            errors.append(stderr)
+
+        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 self.call_args["timeout"]:
+                now = _time.time()
+                if now - self.started > self.call_args["timeout"]:
+                    self.log.debug("we've been running too long")
+                    self.kill()
+
+
+        # 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.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.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)
+
+    @staticmethod
+    def _cleanup_procs():
+        for proc in OProc._procs_to_cleanup:
+            proc.kill()
+
+
+    def _handle_exit_code(self, exit_code):
+        # 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!")
+
+    @property
+    def alive(self):
+        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
+            pid, exit_code = os.waitpid(self.pid, os.WNOHANG)
+            if pid == self.pid:
+                self.exit_code = self._handle_exit_code(exit_code)
+                return False
+             
+        # no child process   
+        except OSError: return False
+        else: return True
+        finally: self._wait_lock.release()
+            
+
+    def wait(self):
+        self.log.debug("acquiring wait lock to wait for completion")
+        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)
+                self.exit_code = self._handle_exit_code(exit_code)
+            else:
+                self.log.debug("exit code already set (%d), no need to wait", self.exit_code)
+            
+            self._input_thread.join()
+            self._output_thread.join()
+            
+            OProc._procs_to_cleanup.discard(self)
+            
+            return self.exit_code
+
+
+
+
+class DoneReadingStdin(Exception): pass
+class NoStdinData(Exception): pass
+
+
+
+# this guy is for reading from some input (the stream) and writing to our
+# opened process's stdin fd.  the stream can be a Queue, a callable, something
+# with the "read" method, a string, or an iterable
+class StreamWriter(object):
+    def __init__(self, name, process, stream, stdin, bufsize):
+        self.name = name
+        self.process = weakref.ref(process)
+        self.stream = stream
+        self.stdin = stdin
+        
+        self.log = Logger("streamwriter", repr(self))
+        
+        
+        self.stream_bufferer = StreamBufferer(self.process().call_args["encoding"],
+            bufsize)
+        
+        # determine buffering for reading from the input we set for stdin
+        if bufsize == 1: self.bufsize = 1024
+        elif bufsize == 0: self.bufsize = 1
+        else: self.bufsize = bufsize
+            
+        
+        if isinstance(stdin, Queue):
+            log_msg = "queue"
+            self.get_chunk = self.get_queue_chunk
+            
+        elif callable(stdin):
+            log_msg = "callable"
+            self.get_chunk = self.get_callable_chunk
+            
+        # also handles stringio
+        elif hasattr(stdin, "read"):
+            log_msg = "file descriptor"
+            self.get_chunk = self.get_file_chunk
+            
+        elif isinstance(stdin, basestring):
+            log_msg = "string"
+            
+            if bufsize == 1:
+                # TODO, make the split() be a generator
+                self.stdin = iter((c+"\n" for c in stdin.split("\n")))
+            else:
+                self.stdin = iter(stdin[i:i+self.bufsize] for i in range(0, len(stdin), self.bufsize))
+            self.get_chunk = self.get_iter_chunk
+            
+        else:
+            log_msg = "general iterable"
+            self.stdin = iter(stdin)
+            self.get_chunk = self.get_iter_chunk
+            
+        self.log.debug("parsed stdin as a %s", log_msg)
+        
+            
+    def __repr__(self):
+        return "<StreamWriter %s for %r>" % (self.name, self.process())
+    
+    def fileno(self):
+        return self.stream
+    
+    def get_queue_chunk(self):
+        try: chunk = self.stdin.get(True, 0.01)
+        except Empty: raise NoStdinData
+        if chunk is None: raise DoneReadingStdin
+        return chunk
+        
+    def get_callable_chunk(self):
+        try: return self.stdin()
+        except: raise DoneReadingStdin
+        
+    def get_iter_chunk(self):
+        try:
+            if IS_PY3: return self.stdin.__next__()
+            else: return self.stdin.next()
+        except StopIteration: raise DoneReadingStdin
+        
+    def get_file_chunk(self):
+        if self.stream_bufferer.type == 1: chunk = self.stdin.readline()
+        else: chunk = self.stdin.read(self.bufsize)
+        if not chunk: raise DoneReadingStdin
+        else: return chunk
+
+
+    # the return value answers the questions "are we done writing forever?"
+    def write(self):
+        # 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()
+        except DoneReadingStdin:
+            self.log.debug("done reading")
+                
+            if self.process().call_args["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 NoStdinData:
+            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.process().call_args["encoding"])
+        
+        for chunk in self.stream_bufferer.process(chunk):
+            self.log.debug("got chunk size %d: %r", len(chunk), chunk[:30])
+            
+            self.log.debug("writing chunk to process")
+            try:
+                os.write(self.stream, 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.process().call_args["tty_in"]:
+                self.log.debug("we used a TTY, so closing the stream")
+                os.close(self.stream)
+        except OSError: pass
+        
+
+
+class StreamReader(object):
+    def __init__(self, name, process, stream, handler, buffer, bufsize,
+            pipe_queue=None, save_data=True):
+        self.name = name
+        self.process = weakref.ref(process)
+        self.stream = stream
+        self.buffer = buffer
+        self.save_data = save_data
+        self.encoding = process.call_args["encoding"]
+        self.decode_errors = process.call_args["decode_errors"]
+        
+        self.pipe_queue = None
+        if pipe_queue: self.pipe_queue = weakref.ref(pipe_queue)
+
+        self.log = Logger("streamreader", repr(self))
+        
+        self.stream_bufferer = StreamBufferer(self.encoding, bufsize,
+            self.decode_errors)
+        
+        # determine buffering
+        if bufsize == 1: self.bufsize = 1024
+        elif bufsize == 0: self.bufsize = 1 
+        else: self.bufsize = bufsize
+        
+        
+        # here we're determining the handler type by doing some basic checks
+        # on the handler object
+        self.handler = handler
+        if callable(handler): self.handler_type = "fn"
+        elif isinstance(handler, StringIO): self.handler_type = "stringio"
+        elif isinstance(handler, cStringIO):
+            self.handler_type = "cstringio"
+        elif hasattr(handler, "write"): self.handler_type = "fd"
+        else: self.handler_type = None
+        
+        
+        self.should_quit = False
+        
+        # here we choose how to call the callback, depending 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
+        if self.handler_type == "fn":
+            implied_arg = 0
+            if inspect.ismethod(handler):
+                implied_arg = 1
+                num_args = len(inspect.getargspec(handler).args)
+            
+            else:
+                if inspect.isfunction(handler):
+                    num_args = len(inspect.getargspec(handler).args)
+                    
+                # is an object instance with __call__ method
+                else:
+                    implied_arg = 1
+                    num_args = len(inspect.getargspec(handler.__call__).args)
+                
+                
+            self.handler_args = ()
+            if num_args == implied_arg + 2:
+                self.handler_args = (self.process().stdin,)
+            elif num_args == implied_arg + 3:
+                self.handler_args = (self.process().stdin, self.process)
+                
+
+    def fileno(self):
+        return self.stream
+            
+    def __repr__(self):
+        return "<StreamReader %s for %r>" % (self.name, self.process())
+
+    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)
+        
+        if self.handler_type == "fd" and hasattr(self.handler, "close"):
+            self.handler.flush()
+        
+        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 self.handler_type == "fn" and not self.should_quit:
+            # try to use the encoding first, if that doesn't work, send
+            # the bytes, because it might be binary
+            try: to_handler = chunk.decode(self.encoding, self.decode_errors)
+            except UnicodeDecodeError: to_handler = chunk
+            
+            # this is really ugly, but we can't store self.process as one of
+            # the handler args in self.handler_args, the reason being is that
+            # it would create cyclic references, and prevent objects from
+            # being garbage collected.  so we're determining if this handler
+            # even requires self.process (by the argument count), and if it
+            # does, resolving the weakref to a hard reference and passing
+            # that into the handler
+            handler_args = self.handler_args
+            if len(self.handler_args) == 2:
+                handler_args = (self.handler_args[0], self.process())
+            self.should_quit = self.handler(to_handler, *handler_args)
+            
+        elif self.handler_type == "stringio":
+            self.handler.write(chunk.decode(self.encoding, self.decode_errors))
+
+        elif self.handler_type in ("cstringio", "fd"):
+            self.handler.write(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)   
+    
+
+
+
+# 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
+class StreamBufferer(object):
+    def __init__(self, encoding=DEFAULT_ENCODING, buffer_type=1,
+            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
+            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")
+    
+
+
+
+
+# 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
+class Environment(dict):
+    def __init__(self, globs, baked_args={}):
+        self.globs = globs
+        self.baked_args = baked_args
+
+    def __setitem__(self, k, v):
+        self.globs[k] = v
+    
+    def __getitem__(self, k):
+        try: return self.globs[k]
+        except KeyError: pass
+        
+        # the only way we'd get to here is if we've tried to
+        # import * from a repl.  so, raise an exception, since
+        # that's really the only sensible thing to do
+        if k == "__all__":
+            raise ImportError("Cannot import * from sh. \
+Please import sh or import programs individually.")
+
+        # if we end with "_" just go ahead and skip searching
+        # our namespace for python stuff.  this was mainly for the
+        # command "id", which is a popular program for finding
+        # if a user exists, but also a python function for getting
+        # the address of an object.  so can call the python
+        # version by "id" and the program version with "id_"
+        if not k.endswith("_"):
+            # check if we're naming a dynamically generated ReturnCode exception
+            try: return rc_exc_cache[k]
+            except KeyError:
+                m = rc_exc_regex.match(k)
+                if m:
+                    exit_code = int(m.group(2))
+                    if m.group(1) == "SignalException": exit_code = -exit_code
+                    return get_rc_exc(exit_code)
+                
+            # is it a builtin?
+            try: return getattr(self["__builtins__"], k)
+            except AttributeError: pass
+        elif not k.startswith("_"): k = k.rstrip("_")
+        
+        
+        # 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):
+    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
+        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__":
+    try: arg = sys.argv.pop(1)
+    except: arg = None
+
+    if arg == "test":
+        import subprocess
+
+        def run_test(version):
+            py_version = "python%s" % version
+            py_bin = which(py_version)
+
+            if py_bin:
+                print("Testing %s" % py_version.capitalize())
+                
+                p = subprocess.Popen([py_bin, os.path.join(THIS_DIR, "test.py")]
+                    + sys.argv[1:])
+                p.wait()
+            else:
+                print("Couldn't find %s, skipping" % py_version.capitalize())
+
+        versions = ("2.6", "2.7", "3.1", "3.2", "3.3")
+        for version in versions: run_test(version)
+
+    else:
+        env = Environment(globals())
+        run_repl(env)
+    
+# we're being imported from somewhere
+else:
+    self = sys.modules[__name__]
+    sys.modules[__name__] = SelfWrapper(self)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6fe9bcf19332ac1d2afa17541dd8edeb30960653
GIT binary patch
literal 41826
zc$~d`dz2*CS>L_Yuj!umW9P9iwN_ebB<({FYbDF8$7*+_T}zr>)vPqDvE+77SItam
zd%AnNYj$VEEEun`g~5Qa0RwS@!Gvdkm`7{^;ot*#69Rz*av+354k70xaDe0=5^%oX
z_uYG|dUhnwtPEOBb=9p~x9;P+-~0ROH~yl(lx=?e#hNnz-A#W_(O>6Jvq}X@&C^k<
zd94CZ1sQe4Q}c9^RiUS%jJlyzl*RG&ylRdq9p+S!Q=wAhH?;bYRt>GRc`&c$^D4}%
zsGx$pnhFZ)b!w}}wp&#5MSEUS^Cf%UtLA(8yhmN>Q}sSI-!DxT)s+ENA5`;$a$Qna
zhE#o6%@4a*j;Q$&+s>$(ALH|0b!CsLkE{7{>h3uD-pbMw)bNA~dsOo=dAwg;nN;;D
zH9sZS1AM(#&F_=zL3L%nsvl7E2UPu_nm?#?;}Q4dRQkywb!DHb-<E#zK=+ewE`wp7
zl(Gs-D!N@ohwRB6D!fzCl+yq6hwYH=QuBA&^Sjmj-S&K1%}?9&d(`|r_WWKof3H2i
zPtD(F&yT42Bli5Lnm=mKkE!`%_WZb-KW@+OSM&GV^9R)Y1NQtuHUA)=kEkmTsrtLr
z{JY#zJgnv)w)IY^`4cL9M9n|S6OMIwx0-)Uh3`@GC;9le0M4iipHPbgdSmLt3iI>I
z{F5r!qrxY-bljCbrNVnvbc&Bp^YJtv&+zfATFj~WXH-B7{475?>7G2N!d?}fSHTp|
z?R(WSf&6nS*ozwT&#Pb`pS+-g{d_W`f&+Zgr-Fkjyui)f=9;^xg0d~W-Iczmf<w0S
z4p-`9KIUgtaHpETq=Lh?#$B$)ODeeAPQkP*eOU$f*wTAl>6{Agvn?EPrI%H3)RrD|
zrLU;qxGlZkmCmc+0TsTFOCNNlud3i79`I`_c$W$X`S!!^_WM;>QNam-ZS%X8-WcaC
z{s9$LRqzN;>4KVT(3aIYtl@$d_HwwOf=Bt0^A11)H6N(p-TbOh1&^uVJt{b<g2z?x
z1h3zsTAx(mqB`^1keXjoVW`3-yg;vhM|Q5Umw@P16)vM!cx$A>*HjpBg~si4lX~?!
z`Z_g#MTOMsE4bxKPf{0Vr=JAgBebU;IClS|oda~T9LKHBW5<s#Me*|5!m(O&<#=_a
zxwu%3k9U@%FVlZBae?YxiyA@mTBmaZEi9K0cgj(xTw80m!$w@+C^s8)=&ZF`&2}6H
zbXl%PjkWc1wLur1^<(8$J*;-Za@6R=)q1_$TIiJTKlZ=__m`*91L{C)qZJ)%ZN$sX
z#<6C5iLZ}SwR_5o&34(m-oD~k1WZmSMX6~`hZtj8sy7$psziqun!#E<ytL5@r3`~;
zRoih?txLfe9tq=CJ6sHD7(v*mHG`<J)XC9}wRrL9qf)Pj4zqEa?wr4nH1+gG9CoO{
zLjj(6aV=a6`RwdUE8a*ta$zlQt;Og_oTtk()iw=AZliV(uF`XZc&^=Uw*4?(Yd20e
zgU}HyRDBxLK0ybfJRntyiIiqw$|!%9`w{&q9c!41BK@J4r}5Z9YG6~VO{FSy;_(Sx
zX843JBX03^LRBRKSFNsRRf7Om?rbszEK?cvSBeO*aGu4<<aIjBspcfr&tk*V#dwql
zm_CAbw6x=Gc|+d4!E<lO+c$Xj4S9J(L2VY~4S8%Ox^QpE3nI<(2(zHQv|#kmW<EJz
z%&76}Jt{6@L8$#3M2&jYCg0Q4t=VZT#7w7;jwcBcqIOs#D1M@IhzgD!J64`P9F$j@
z?eK^N<4zcs!(*K|Xs*Tg?4TL}*gYLE9;H&b?4O!>tbDkQ)7eXBF1&Q<vC`p=xjO6n
zxSH;>khi#2uUBd-0mF{G(4JDG4EHn5Mi}!0?X^a&8i$oywH0G;)M;~~GK02&AHfw)
zhX9FZpE>o?^Oq`TXHH)@bAIMo21nXpVcU1FuT-K&6jv(KMZ3#7VSUl>rxSkwhe5j`
zV$(TPq~A&q*5j(XgTCSp{hB-I-%4j`8W7Hh&J-PvFE>}h<4cXTW-YE>4Ub=o7YPp$
zUho0@0jk}}d!mScnITp3hV+y!=t(bqp3#T&bT%18qq-7SD!d&ll@-Pem5M)%0ZiEU
z@3<$JoT^2nd7bxo_s&L3jcWbudM#|lQL}OL?(K2Am$oav3wzc-h{J8W?nIgHUPm=F
zLK_ENINe-XA%r{AjGt+)HG;qGF5FIczbWYg!}s(SB(}L_TJEV#zZ%?kPYqqlq><Do
zuvm(Br^diG`c%xgXKHq?F{-k$r(Q3qW&w0oH$0^;Z!+KhdR8^>U>Zx;<JSpoWPl0?
zREdlet8*ixu6wF+7gfm^^g=I4Rm^u)B=jh+JxOP?3_>$ZYdRx0W9Z7^fQz?`k&34o
z?lHiq5%b<`*DB$9jdo^jIb@)GF5KYecvHnh9c0ttnd(A^*LNBhvBytyHGnbKpr&@Z
zObqly=PJ^JZ^HwV3>AGkqx$rK%6a{|q^D@}?D0yPmj;+3YnSHxl`^CTv&IBKkTy-^
zs~%?UdIl2*K3C`r-Du_*yU*%Il~*Tba{YRNZvf}&^>e0>?oLHgBZo)Nymk~6$h;{J
zv@S>+ltzQT*!FYO%<77oV;qionc?1B;F?A6?-IaDce**Mw7hy#?q$*XEYsu}rUg9d
z%??j`s~Rs07F~}zgv)SRt<}Pg+`e47@LWR87g2l1OM|AO4lPUg19l%#&(P|{Q<u&$
z5c&7vaKyIXY1N~cv1-`38nv4ZADG-QH}P_ZAP|qg-Xs<y@9wmlOYQ24{{Wi0-*pwx
zBrP*uSPv_UQ9ayo=h4Qs<*2p{_~`8Az85o-gn#>0k+5-2?<MTGNB8TZ$IIfSf$Cz{
ze%Dyo*1MTC*c4fNKd+HtPFOg<;hDP3Q&4>iN#@#sK#X@)_;{2{{KxD>w8K`tS_^s3
z1ob^)XLrZL9^`g9&HB}_VkY+Ucxu4xsY0$_?biiW)D9LsJLuu`pdplFhZwM95CUCo
zwxgwp@JS`%XBjE9cTOwu(66Q-3Xt%8b7?7TJDPwyOcNeej;YLnz<yYRqcp4~Gjuq~
zf$zde9;y+X6hK4Ja4loDn;Yg+D#dWhih|)J3?Z5eIvbZVtX#~xflcNrQJPiBcIE*b
z?sN+LLtGQFMu0ni+Ae9W*@(k+*%EacBC+YrXb{Ge5ep;KToO>2v=94F?_$xJN%%LY
zRuV9$e+4zHYn~Z8!1`I-zgJe+f#zlk^T;SMc!Ff%J7wva0t_YtsDzE`LOl%pv&t}C
zQDd<wJx)e(z}_Ut=<IkT=;d#qTAoKzpl$k99EJZ34reWj3BrZ7rS1CdpWDsspQpRO
z{dd*pkZJro?(=);?(hCx^qIM8%X+-?em_Tdf4{rmGt;0C375#|leYvz6E6>smml68
z4|$62{%gCj^13fy%;I0nvc+1XLA$|lwKrp~KT;`i_`}^2u|Zo|6<W*%ok^ZAv-Yx}
zM4M|ad-yVMFN-QDa3dwCW^vLBovcxfmH4Dzh5c%N06JG(4yvG!Plli*#mO-AsyG>e
zS`;Uv(3j$549ZZP?14TJC*vv@<&z1c5S*U`lHEh&tYJAYM_6t~vZp=9tq_`(#h!U)
zhYt&q<?C|#<|IKA%fbcK7-oi8AZ*d&G9sCfqYEIkMl~QO8H>%cn5`(6Le_cAJx*A>
z#z{%Vy?oM#6B4_r5vaqPdkNJSQ9x8K8c@-op@U4mNgzeNf|>$l9CiggT+r<2XQfr9
z_N*SCYK_ZjiB5avw3ptrUG2WNDDS885|s~wyX2nGSy7o*MTd|6m;xG-QDR8F#(sB;
z`U-^~ZjJCQ?RwPROz&Ga)B6_9P~D4GTM~@|rZ92w7%xnQIpmx{8U$vWMRg;mWmQnv
zr<w!u^d2Zp^y-ryPdXzNZ04B=%COEI05sWc<-_e`$BtQr#y#a)wO+4)(9jLmLnZaP
z?irfQ_m~AMGl{wu5f2Tt1Pwt(uq<QSR@4duA#pL$>OON)X*3t=&Ds^_!xxrVhJorJ
zZdRy;4*J}Qq0DP6m5B*+%s85x9tZA#T*lZX!IG|@+=JRm;Ga;2xoTI_jI?PG{d=Zb
z?}RZLt1Q!yh$s`c9>o<_qTr2}+G`=LCX}MhwR%vwRyF*&L0gQ;h*1n%VI!zer)f|u
zL9|f7oHzOfde7ye5wfReeV@@Q_vOMx>X=L?y=1!Nu>31HT(;FZaS%09${D=EYKC|s
z>&zEXU)s!Xc3RbIjfxqxKerp~4M6yBs5ZmIBCkhvMj!Hqyb)%R=vXLvMV-&}(!H_F
zA>HrYuKTmSsz1}KO8LA#q)K{Nl|1vCDd-_Rn24cW%rhhmz_1kxpd}OplH#ISVjfPG
zD_Jf7eVCCYWi-Y-b2}a=9=l5$m5<WnLd&}Zny+)+a}sXsl&;#SH3?x)K?c6S0t;bj
z!q=?<zUU=DgxvEkmH-9A5{8asU*`?NjS4F2u_r|+EvyYvS#<?NU-JqNnpGD*R!6|)
zH5}gWzy%wA_WZLmr=Fj!Tsn8^Qf2nsg_oW`L+Ad5xysqgr_WxzbpFDO4?cxAi+~!>
zqysP!q%fTfn|SSx=e16Ek7%P^QMyFjM$sG7`v`ajbxMNLc9zH7ECZ1_Ku9k?FILjW
zTUc#8_Ly7kG%(&`KL&5DA9(D^uVOz8uV^;JEp(_wch9_ihf1zPk#^niTo2HpRutUA
zyI1M%d2MA{uS?OxZ)W_hjQUP2$zAUsvF|6uxBK_=uBue(VWU!c!F6Ms3Bc5(xOH_)
zOV2A+AvqAVuyWD8FSq^sHsA64jIN(wH@9VYI4#-NeSr?^{}>b9{1K%$_9w`gcn?AV
zgyEp6(`v49kIE83g4Xr`Yi$LOLEG2tAtc9`Y*w#kpv!dzs0o4M62mhQR{Aj*ZsQQJ
zA~7?jos60?XIWwr3FhahsVtE+ys<~#NFbmu;|+6(nP=(${m7sH<_ilKpY$&&tNSvh
zKc<IbA*rw&*qLTD_kIVzp!5}R8I@JG_W_=k!q|TmHCRk?beI}wH2HzEbHJ)kKY!u$
zb0RxoNAkO7<7VqTO?s7uH*99+HC}2&=))QA32TW{NEU6^me(3rcD%T(Iu6(4c`Pi%
zIF-q$g3hS@dO#1Te%Al?)AbOM>;bVLW>t#x9(9`<1gyDFfwV=U&x!5gW5FCH!lN_(
z1#~cLXT|>j4poN+nk~6EX-ndb7LV>~wN6vZ7N>T#7A_Fav`au{Bv+{?6nj$Nj4<vO
zgwgD^i1P*$ZL1{~^>D->4IyMUHL$Db4J(SlsGXm!TzusrfsM+VtX-wT7{groqjnN*
z8S@a<s#ID!C7JgoV{%-`V{WCIN9?eghtyWh0*18dl<(L)n*9T^Zgl=NnH{@;3C$n7
z^`ZD5QH-F)5~cJQF^q!=FXdKdT&47+PJ(^i>r$5bA5~q<2bQB5FNf`2sox1!Rx6b^
zQYi1H-eAja>ZC)&rFHeA-Q5o(%&hFV8-3gjC^D;2qf@C|-=;HefzL{32<L%WJS;1#
z>`pZNG2>tcWrI6oF?VtI&0Rr;VoPTGm29}My%IGbDd3G)qI#Wqh)z<i(^akGsyQfO
zcqN=zjY+@&#GIm&XkjfDOty8|Fomrh&z0;k(|V=yL2Y%iz+q}wr&yAlowAqcurZLV
z-|SrDDIye_3B|h{Y4X_SAiDzca-21r1&@f5LdY8vl@v1gMvDv>68!63+lA+mq`zBX
zIRuyC;Y#JB+SwQ~im*0~w4_G1!D?pjI5x<MNnIimdR1>WmblACY-~rZTka&<s#LyZ
z8^&mt9b?N>y;+TAGC!@}0sF-5R4Si%$G!3oxz)YJ4hasA8q}RnCEc0ELn*~7>r|Ot
zhhW50OLBQDY{wgkyv1BNv%`J|2Ow#G4TrC@4^=84_mztOVHCfK12C@-YrX$*96p7^
zcjEBfID9V-f3IsmJ3;5q(_Ls`JFuYTb@Az9zBn+L<A2#=hNS;YaVYPxPB%~IC_!0<
zSplcf1u(Kkx1+5_cZt=x%X?Lr0cUt3WSVs%VE0S@$;eMuesc7ahyL(H&;!*V|CH#b
z7v6ni!GKg7l%FB_8J3?Bd3IE;#^`4cTrc^Z6ZDgVJLQQm4;RW4!CrVw@&o&&$^rQ~
zC_lGJD`j<Jz5sv1;9;m&p<6Cm*GSDO<vXoP`LIzbhy8FvfODjH!4AMTQnbowc$zJ9
zLvW2WisgH)V);I+SUzGE%SWwZ`IuEKAGeC-`;B5bcz{o)pkZdw9%zy|HdeYt4Id_2
z|3w^LR!monLtE6SEUYbdqBp{bt&_mX@c1!>d<|utt;B8+SmcPE<%YNkq9Cww+GkiN
z*tqKZcpdEa%$)lx%CLsDYobnkoZ*|X4Q$<P;*K}4u?pstdcq8h@sesswODu=t`Mz+
z)`P0Sx<q>lg=9fa!QKl$8iwrKHOu}>aCofN+hH}>C@+N2JSMJUorkE}E6ufXt=b@_
zzZ@}7AC|8zhmCT)3|C2`FQrp?m^GhF-+}V*3+qYe1;Izc@xZSlqgq2k+quXPbV62}
zOqs4{*|0jw-ggnJ>}i8r$GdDd8hZi<xa~0hU#MOQg^A@Eu6NiP&Lles7prUaxN_wh
z^rdVdXVuT{@Li22D-Qq6Q7*5KxYi*)j(U!@aZ<*7*U-f!V7sAfW1SF=rjiA+z)24K
zM~o8me#7jr5<Df3gEFu~fXoAiUxDWYOK^fbjhARVn!2c9vI5Z_kEF|;QrZ6wi^~({
zW>=mCjQ#&g8-|lz2}mG={uG{W&Uol3S4UaOWgJ`B6=zc${vKu%v59ONp2cV!JzE}l
zt~|1wO;ePSgk8(l(ZW_V%@`TsJs93kVJpK&SlFM8-FD4|*J)euPIt^2bAZMNk9GGQ
zHVTON9q`3g6a6Q4Gkag8y9LkAo;TtRk$}`se?w|OfUV0Z_AgaRaYif~fey{ydR?XE
z*)y-bk3F0+*iZqRS|-HZ)i|k4$xh6XH09y$Nx6D_6(Sd#%}{GZfHvc5;Wquv#k_gp
z^(1aEd9^;v9mvzG&nejC*!30n_$2D#?)I>IYth_d%NmE1(Dh~yoAYG5tbT^x^BFZK
zcDzN;x>fM%b;@)&-ymT-3d)^USR-&Q6C|&#gpC-soX5(CJJY=uThy8~$se#ZAc1?{
zLXdaFtkMZ%5go#fP7H`9+)TKSwpg@K4^xjZ2O4!^|AUUMblCY6EnunOF)g}}qJ=9#
zM@(Q~3A##HIU?cuvh5>n$|XiU27hsns3%O(IuEcVoGsNm#$&wKiI-|uYjM)~5y07<
z2BqZ*!uoWH1dstu5U0m8BgC5LY5(?lL;7HX=<*i!7G&o()5s0G?HnuAV&1XTb+{>K
z`9zCCZQ3c+_OsLy+r6ExCccpg|BAQz-=Hl23hB#-MOjo5ztWAbp4!dauq?LNY_AZL
zO|x>$a}o+6t}JVp)LzZnq(XY@{S_USm`1JtFUD6&_SzrtUdypb+q(G$EcQ2FVApT>
zfEHErq|myIaW<b|6Hp!yfV+F`-A(o<6c=)iqWBv6`wRLT<-N(a<DAhXig%f@0GqaY
zRCAAeQ`WtSAD6yRjjSn<T7nw9u2;k0Zl0TCdW;2`Va~}vi39INEWy#EtQI+X^l%6N
z#QpD|DQnDR6(c}L)RF%dhsUv01-pEVObOhtHQRyRjV)u}yvBRVSouO>rmpbu>W&wj
z31W3=sU0rC%iO-{QO_U~SOEJ~UuKkc!7%+D$`;f(?F;w-X1%+&0fpaBK;h>B3TIyX
zJXRrQEDEIsJ15Z6u=zWuS_f&%WDL2ISss`0nj3HnrfFVp;L$U$?FSfPS@OnOmI8#A
z)ZA*nxEJO)M1a~a^CF^Zkr$EIoY2w@Pa}*0^}AZjxz_r5UCon>->E%3!>cWk1z0u?
z3n>HTm0MM;LAgo{Wy7J|a@>T~y?lPLywO~{yB(I-I$#vx26&8p0q=2v1#1W{ST>~`
zR6}g(F-u!(SL)%_u+EIP|La)X-UP>T1WuzC`d?Cp|B2VARfZixukg6(7$bALA)McW
zg*Pq_z)}AP3x^5x33i3sq5toc;UgWq35x{u!uYwfwouuHLnG_TD<C5g(GWCs(X*;-
zozD~q+m7o3LEj{yp1eA!hjiK7n;91?k)`{PZ90FWtZFf`EOyL`R<Y{dP0u<uXPKeO
z0wUL6HvY*HcXH;nvs@z&y1Y?lh{+RqFF5v!3mykV&aIv%yj@_tU1GdV1@ynZOouso
zGZFQO-BKcnNJ)xv?z+SgD`FVYg1GvUJ-`}jkNJXh-G1ZWgGiT*ZOUq`eh5>~N`(*L
z@G}mv{45S|cJzPIAt2B?|DPyBhSypF?SKF0>^0FxV&VJ^RQf$MTf7-2m;^62gjNqR
zXsk5|E)xD3UWxv1TJYI1-v=Ia5zugmp<$3X<~^R!<P2e<K_WtZ-Y7xDsGicdZR1yP
zkNQexfS3cFW>ZoGv%5@GS+BxGmFX!c>n-TTp`v-i>(=ex#5SO#8++eEF+SH|lNaI4
z*~_VpGHGYR5Au<s^dH+z_n<fWTF<HNGep)i>SkRUOO7c;`M7~K39LHpJcgb~>CJaP
zSI}C0$h+A@=@#@s=|+^^<;-S5xC!`d;<`>nrlG)Y3-*;@#K}sLpR;=-rLhJcO~k=n
z**?A@nZyxV<$-M!l5k$xwDWj_)-=&Hi?qs;lD6CR7A6o1!;hsV(3<;nx(hC30%pCS
z+rcI=<T3M}Sn>R?$KgXb+}KTzpr-uzmL-L%6EnqFd)ed2MI)_AjE-Gc@DGf{X{VYB
z#7_7Io1b~@StTMHkhk2<tBqcuXPV>3=NcSRgrHqR-}4UXGMP6>dG!&&)x|UxR*Y#q
z<77t|7&M_^s0P|9h@W1+lF-O0B8t>?VQE@>Y{cZ$k~S4i8m$eC+rZ`ovlSK|qe1W^
z7;q7m<IN(FDEJmw`HD;fNC;)K#W1&(Gi}km5x5LeYpbuYMLZf~cD9JewcP}JEUCz{
zvnGO}I8-tkSDPi;E;P(ub-hoSY3Y;kXW3)(I`yZYeVRz%qcghI*E~g6leh|)zv^q=
zFZ0l^;wcsHHFMAh2|C`V;{E2qfV)L4+KDJp`vYooz_mZn)&9WN_6Odw{ee{b1Ijv`
zit}U-UXL0%0`y*GZP%=S^#6e}+D}oDf^f2KR3c#7B>OUPySBi}(rNa&{skPq<o3!h
zx(&mIaIn4pe^ExMCj1o>U?#huvp)RY{r_mU7;D-SHw1rO8PZGKT-c78>I$xcQk_|>
z<!Xm5aP0YL<ClHr_po1^t*{{&c+l<Mgn>&u3dJFky%!nrwGs&)Oj*3rN|EqoWt@x_
zqjradLjONojLDHp@?eQ;K>f>4C7xtE#23cK!SAhn0(iq`ya9<jpBaSo?F|xz>ZQLd
z$sc3hP<Eg0_x9>N*%I-IgL)`CK*)C}Gv-ZY`n|l@&*cNeFHCq-bbgnf&{G*i{tXE=
z=oqKf&K6J9ovDYV3Fq(HvHq^Nsy3d6FJ#JY#E`}E;gExqJr3@ESs5jLtxj^sN*FIU
z1OKOShwV*JoUsj><bpV=nfnZhEMqdO&j1EiORPz3NMiF5^FM5YVq4RnbuHsga6k6P
z_d@H5{v(<;lPzfXFI({F*xT!+%22AgJX6T?b-q-{=wkn1Z?V7Fo6&>&ilx5E;&^e4
zEl-(Zj{dUI-+cMYU-lPO%J76`VZ+n^P}cAiGR2$Es4&Y>Ph%W4kX2!UV+tgpi-bZI
zk-H>`Cg|l$7fsM-LJ8*kZ5+XXjUyPeaRfs)j$l}YBPNId&yMnCpS>Kj(FA*JG{Lxy
zCYa!t4%wQMHl|>TTNttBdu?RFJ{wRFVaG>6V>Rq1z>V|q`n|j|I?hU$-%qdgHUP~s
zL0#fu^1Kb>qPct3GirI!$s3`H)$&F!Lg$54c~7dms2V@v-hy}Tzq#d{X}>YWde5kL
zn|nz=w`(D9`mkLK_8yT1+7-|!?Z3HFkL|2b_h`VXWqZVRpdSR=Iz-{&Aza~^VeZ;6
zU<jh(Uun4cBeg+1Doz|@AjU%L-P7aFyVHzmjA<dQq4N!Pb*IeCS_Va{*2C#Lc}u#@
z=*H4F4X=~+9Alt_v6z%vdsR}Fe(63dh_dn{Rm7=~P-I>x9)4Z-3&MzurHhNUm<V@t
z0OUJ5XdI%au5%iP=r-&o!^Na#=D0Ld<~q0Y+clpbV!~fiX0RnUX{m>Y<?0UeSo(HR
zO&JeGGZlxG*h!|upGG%slk&S#<;k<eQ1RWnvE#YvwTw<d%X9)Q_o(Pz)jB|D_o?WJ
z!u3&MBah*ta<4!g!O3w8RQKBeod;|<&Vyj)TEyDM57|oZQpx$lDn6m2N9^UJDt<T6
zkLhn8^_K@;dF-ujyeEwp#_Y5ukjGs-3A`0QZvAm(>pY<naLc_Vlos9&N{a%eKbmz5
z^n{9@v{5m{z_-R^9-l(Wq)oMTEz#%GRO4x6QF4729+!Zs21fm9<W?GdA#)OEXH6}f
zKZ9CWP9Ea39B-o%Y!<!C&dd-^mY}0^SQRkO1-l3KyS@yosXhcI;qqZrO7(LD_wIC;
zqtvZqph(2qzD|QIv6LcENgQ*09+{&|U-BluOZ1rS&NxA29~>IMXcu@9->clN<i-S-
z_p`i*`_HMyC;0*#IQ9Mcx9|Hvy6=;?cQD=eV882i*7kakd%f58y2r%&M-II9QLhKM
z*Mqj#&+)w%_&CGIa~8HPq;6g0<9qq|92N`}UKCR>So9hHhrzJ?h%(Im($|dr#It|+
z#)qHue^Bv67=H!PqJ9o%U$$KLN_D+X?DG#;e!MfNj8QWTY&;S(!~XZ-@Dn)vI1WFl
zjO-vx?`JK)+##N*5>(@=|Bq1qqUDHv#MZOqZ5hyM-?8Rwe}Rg)9#-ObL!4@3vAu)6
z$2mJfaYRsMG2j^EN~eyOxw4X}0S^wk9O|lK3T+)QzBkp8LdjEr=pjE)M%}km<M&4$
z6VEKBxv0azPT+wTo9!#?$z<JVFU($MwVG%;;H@FPY4y-JdvwCM69wX(5pUQcm^auh
zy58)B#zC5RZ&6pTwH{=F0YUp~?OJHV!`t;}C5l;!@t;5c!ud;;nG4UHfBr0wmRo99
z8%v@8C(1gH#2cJ|124T$@z2cpBG`oSH4?+3Hzcos_<pa4wW~%qN@vn1_DVMScrXRb
zi;7XD9WF&3V*a5Jt%uddnv9lY0X!nTml_gJW-4*iR)_h0Nh5*@D<rTeavb}Mv6`Mi
za@_)5ybNgtZtr;Zd4${XLL<7Yg6KQH*ACslf;*=*m*6^fdVo)=gsE}l6?X~(k{t?G
zs~X7iv*&2DBORz&H&Jxp?CEnCOmW8)<AF=3XHU&sm}3cr#MrR9GS}u<!TtoaJ6xWs
z;?uzFTp2Jnbq10Vm?A$^#v8xF(^iprkyyk=Yt0pKm77YMoZicpaPwV79AKG>(_41C
zf6(n@7P3hkggG2rNOHE+Y#soMbFlZ=<B5dsMTwf^Y2xrh872EJ^;(Q#R?XbUkZ-yV
zlmhg(QwNnlfw=w8Q8`oe4(3aG(u3ID2a#Iu_a-xZ?CGatA8R-VvqQN7HSCqVfu6C<
zkS^&V-LI&gn$)9uG~2Hx^k`<BpFgOF>G|Qz7~MPQ4QJ_jkLza;4Oa9<bzg2UJEf<*
z0X3GH^jvFnR-lprJ)+0-f$Sg&?|Hpn_vZ&mlpoRKx-T=KNYF=_H>k^|Y~MB>^jN9D
zc~UbaAl|SHrwW^t0t<KYqM-TmVb*9z|65xf@v2?wThwiTf2Laz3e@3j0Iw^nnthd{
zY@iEePbiZjC1ZTXjCfJvP;e?T?#mH}zl*9bV4WecD9E+L+0r(Z(T9P(VM58aYKF#3
z=P#U|ojX4>J0C5@Js&)egR*$%9R0^cR5ArHUPG8b#rly+`o0PyNqF`6hjikhw^oad
z_Xc4g_W!Ll;=1^i&<xd+j64SD+s$ho<C9YF_=wbFL-x*9@k3PohcM09<B9-cMEC2G
zXTTw2CY@6>9Cs5gKp~O!H<TwcPxDye<VTVuR&P2V8`-JVHll-|SQDGkK0EG9j2r}Y
zb6&P;xYBGGPi(?MqG<mEWz=$l-k00Kj>m|w%Fk!4xxt&H4OG%MJKbSV3#^;?5S?Vj
zOKJRZ9t)QPoC&|pMIu*i<^^iYAe5$z=t;^}f`_Sm$G!eOy8Fe{2DsJ!-b?5HdvSP9
zr;RuM^LiKC5&YRNyj^b&yWZHqwJrT>Y}2><zAAkKXZTC#n*=zOlJp|Zlw^EK;n#Ko
zFxuBV>bU$nTsi^99Y%;1S3t3`tQM7?LqRUF0f{*eYjC(-7F{Q@b0SATnJJ^V8$p%F
zpj@p-SHpxq@Zl`(7j##Sl4T3tL9?;*$l;^Z$gj99tn)gfMu?sadz_946wW!Be5+Q*
zNF=T%CfJaq%7bF(vBo6!@X+a{+w^X(>ZPlCuIl>l19>)oTeMB=QUJeHajeX$S2M=7
z>3Tt}pA{dK$5~~>B1R;sGkatedd#FF5k-;JLU?!HrmS536#vbwvsiZKwLDvQeoxIA
zjY@{(+w;^9b|OGCCFncEZZ(<IL9UR4HsS>xNR&psk|*MEtye?Ee{<{mjhTzJR6X!6
z2t<BgJ*yc|jPIx@!|e2Np_eX92+Pv<d^i64;*(5t9Yn>=@<Pbx<;8Y$#s4uZ;yyZD
z3-7)f(!H=zZcA<$s@p8X8{7qTiaCRMt_6!*J7GPn#VIybVsfg&O|=Q}UKJ-o-@~q)
z!eK8CNS5a#+Sku$3%I=waJ%zgwwdLW^l%b8DuNjEf*oV5!6{33miB@FoY4kRHk#LV
z4FAXJp<e_1KV{%Q@AVtED>{0J@4THIV@BDLc#ntkEOAJEdLQvj6N4Fr_#tp;g>6jv
zWz}s%=P<!k0O1XnZjHmABp{OQz%J5J4CqKcdy66Xf9){uHqqW+wGQ5PYaPV8>ul1&
zo4hs7s1%_}Fzm*JF;yFlk(5e`%iPu!mnob(%y8U*xK@@xJmNeB+a~ChWpD=CRST9E
zX1B%<vn?vxc`~_g!Kg4&$$d6Tn05kD!Z$h6+_`1M7MXZv#EwpjGXr{u$U!g++3s<C
zi|zgF`DdRy|NQf}us)cD-mD{!L)$q1?6SWs44b&euh8d9XZ;s$q0iq+cL%%rthRzZ
z?2YUD3`dGY;n!sh;-|4%GPy8Q+y<C&zihEtk}Vrj`i^@xNso<Yt$&gS_vV|KA;WGs
z3fdf$G@F(g>YcS&H(jVf>^1=c5#pR#h7W0#0(}@eUus+-?AR#RmZN%5hE}8ubLCp+
zcG&}fd|sis^Up{|le1@7Z!(9qpc2+B(9N@#&tIZjTovcDmrh-JX_ke3f2{!p%9j1Y
z>XRK$6yo-#+-C8H)FAyCHy_NpOW*aY4)_d%kz>YTHX+BcO?Qbnn7vMu+Fd(F3e-22
z)yDZmhKqXmyl5R#Y<^a`1+ma2S&$f_O*Zs#dvlF3LPWi(912`~8Sj}BwJ>_u$KaNB
zH0!rhCA1eDpnpJ{Em5tlu0`!o4Ab}7jRHMWD+&@^H8*qN+^Lyoc@q80+7VXvc}?XC
z=t-KCr+2qTAn4_+^%z8^(mo=D8W~G-L^v}U_>Ye0VrGEAyeE+f9Ygt_-V$+SX>vRx
z3cm5<sY;%!l(U&GPNf8fK(I~hvilU78}kw=->jEs*DI$QX^A7qS%ZpaIXJUOxahYR
zG3Q;2hOALUp5c{!@p8hLabiN5VbB{aR2d3Eux5M#rXTLO^$>|GI#~RrW}H;)Ah?Qm
zDY74w;oHy&<0D)FEjAlvT6syVd|y{p>q0@*kx%`eBlK{&5r%<%_w;Z#-UX0}??K!R
zf=moUZ8b6sRL8U>`}gZKZnT=|TD2XdM3dda7J_$AWtB~_Jf!#NA#aF&_vvvDzVQ9t
zkSEFdodJG&KVFk~bNrIS*u0DO;OwL}igoeXVAqR3=b_>TWPLF;Kw@RlkOd^>68+Dp
zE{!7yl`sk4$^+GQa+uLw>Kg4%hKx!4BWMKyG<jVFFPa(rVlg+^JIKMl#ayviyniq|
zn4`07F*}$q76xyZ6!awW`J#lLg(lZ|W?;{;X9mZIebrKyzfSGWS*|TF6l12zuRc?a
ztAE?Cze4R^PG0Zdty9h@?wzlw9ptq%J}5SwCGzs-OkQ5&chY11PKws=q=Z4RKUiKR
zff&pW{*Gp6;x~m7XcI|#LrPeq_$K>RyeTC)P#CzV7H}!X-8XF($TyY!a8gM;MRN9b
zFXUdMn(XJVHoPgx81H14b{c`bld*S7d`B+ge!QP>*bPZGP6H)@8m}AV8bhk_f7t=@
zO<^!lrC_U!*ebunz7|wPJO{Z}(bgKXwLZ^wdAx29*ZLsUDsfDS8^K*vW!!k}P{n8h
z26B4u8Ho&$d1^e$wLYrUN7?Z+=vN=18J@&o<0;eAH(9X0k?DRErF(6E4$=DuI5cWJ
zd1y<eH<`dk``9KeZWn<|@n~GUAaSzKVw*)Gv2${8HeisIHk{dr9SMETJO!+Q%;4o9
z?9|#(D{i)3aCKwJ`Cl^};Sbi;^>L7x*9Hy$Lfx2nS+C}aJ=KP<L#)3l%_N(OHN3~}
zXI1WwIaAI?a&jqbgzK&L6CbvFt7w{cox`}`Y{wrdSTA$urqH=JxqkxsGzAm4n{IAN
z%#Dp=V$}r$G_BS-O0kN|x6<j4q&kh2LV26SJ9Y+KsE3^6tyOI!Vu;OM95;m6Or{&t
zwtLsY>Xml5DDht5qBx4atWCGJnCsY3CAPAsjVf1UV%AHp;=mrLY_c0v@&y3~j0)$T
zvPM+760g${TV(QCntOk2Ux#~uBh!Mo(RV@tBMJI{$ikqQUPsc8a&j_h5`;e$c!Vj~
zI^0!D)$7friu=w;_fqY^-1Px0&J&EDIiIC2dXt$^-IwWCM9b-~m>EI@C{cDgn)4wj
zW4yb(N2nyP2X)^z`V0P?Nw=Bl1_!dZpb2#IWZL41k1m1nLI8!=HSSxqG=0M9FSf~J
zhq;s9btOA``Igirj4!bEjpnDeytGSc0(NL)D&d=BFP%_sibRtPizfXdnq)aAfY+5k
zuEhE7lUxC2I-PsRbYcnTfF+hm*lY#rEUe?}-Fo@2-Oa>arDwkjW3<V7rt|<uqL;QY
zHbMf~7%<~S+>!Z`SZuQ$+!V{R?E1I+HD9MkK9f~R7|%dDjK}3mLT>SmyErClAeA}n
zMk+&j#&i}dl1;Bj&aFtQ<4FiI_x|tWfLt~H<~tY+Ev@=&))@dYtjZj=im5Ke#3j`&
zuf1*(W~e06AMrrrt|*&$ry2P>fvTK9Rid9@5-L!|$n&rrsPWsV2K9)z+=~vez7y!$
zfaNk7^{5&p38P?P%bGrT)I{0T#!v!VyoG+!CBFJ#D)E)e7@>_foXDwgiM*ur#_e6Y
z1>=S(DuyW%O#ud2`;xIBi1h$p-68lTwgYj9;@suAm=nV4vpP$Z1znlk(iJf)kbnTi
z!?5DejuB=f@-9|a+aO2RTge@60=w4V<eQ_3dg;t-F)yZjOOKNL**AnkDXKEe-pPIO
z(}cb@Jp`k#UG1aZ@7cEZz0&&uPCeOYGEep?8wUp~SJ^n;unStmrU9S?CjafQ7DiWz
z)|1@J9$~Dn2CvqE-!{Z-CW2+~BIUyYJ|z}G9$|t3)(!%;R3VYsc%!lX#OQ*{wvuoY
zl1q}4W?!@5nUb%Wt}<%?o4tRBHbj7xh%cYL@Qh@SS#C>5S?c~Hhgh#TF_!oLAG=+`
zA?=sDErHkxV%J};?%XQ{FZ)Ss3B>)WEV5V*5D^~pGU|51Z{vEx%Xw5nQt5;)d!r<y
zmWf1X^aSc9M98Ukep<biddK04v|`6)Yc_dP07V<~9J>Wq=UggiTzAD>aV`(h^h~wx
z>{jM52<2GiqpUY92NScQpm$qCfv9e<(C}WP1wK+<Sc}Vx^|j72mYaieIB5DNAOfUt
zJqJw;nyezf#^L4xv7DDKy>g`7X_ig%<#;(Pi$uZjodOyxu}S4djmlytkilG90#`7r
z2`JnmQbYs42?!Wxq=*#aWhMv{dW^0ni71HKZcC;$)}nkZ-VUP8<dMk=W7(9gTJ5GY
zkm)Pv3<jOo#RpgglIIL)J;gixd-QezD6ke8nLlrGo*~vGYbF1jmHhKo@-JA)zsE}c
zMTqyDf4^wxfa5dPz0?<Z(&GK574J7CY$9i)D0*x#gvaJ$$roDbvDYR5q-g!kd-)5E
z<2D_v1U||QM)!T_cHH0X3O)eQ!r;}s_3W0e3~_vB*69OsCU}B11$j(TqC+ruN_<By
z#ErNz0{2S!OeNQ45>^>g_$C=)Z+JhCGeyD89((hQyE)-*7VXWuCGL<T6Yb4jd$Z(R
zr^g`N<3u7R#*};lqWM2XYf)73J{9e^eLKYCK<NR#anRg|Zetdtq#83E#u%0D6NpK9
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
new file mode 100755
--- /dev/null
+++ b/ss/bin/scansetup
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+
+eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
+    if 0; # not running under some shell
+
+use FindBin;
+use lib "$FindBin::Bin/../lib/perl5";
+use NeuroRx::ScanSetup::IdentifyModality;
+use NeuroRx::ScanSetup::RenameMincFiles;
+
+$SIG{__DIE__} = sub{
+  if ($logger) {
+  }
+};
+END{
+  chdir($origdir);
+}
+sub SetupDTI 
+sub DecompressDicom {
+sub OrganizeMinc {}
+sub ConvertToMinc {
+}
new file mode 100644
--- /dev/null
+++ b/ss/lib/perl5/NeuroRx/ScanSetup.pm
@@ -0,0 +1,358 @@
+package NeuroRx::ScanSetup;
+use strict;
+
+BEGIN {
+    use Exporter ();
+    use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+    $VERSION     = '1.00';
+    @ISA         = qw(Exporter);
+    #Give a hoot don't pollute, do not export more than needed by default
+    @EXPORT      = qw(GetSystemOutput RunAndLog GetSystemOutputArray ParseHeader
+                      GetDimensions GetScanDescription ParseDicom GetFileroot
+                      GetUser GetPath unlink_glob);
+    @EXPORT_OK   = qw(get_start_time logfilename get_pm $logger $trial
+                      $email_target $scanpath);
+    %EXPORT_TAGS = ();
+}
+
+
+#################### subroutine header begin ####################
+
+=head2 sample_function
+
+ Usage     : How to use this function/method
+ Purpose   : What it does
+ Returns   : What it returns
+ Argument  : What it wants to know
+ Throws    : Exceptions and other anomolies
+ Comment   : This is a sample subroutine header.
+           : It is polite to include more pod and fewer comments.
+
+See Also   :
+
+=cut
+
+#################### subroutine header end ####################
+
+our $trial;
+our $logger;
+our $email_target;
+our $scanpath;
+
+sub new{
+  my ($class, %parameters) = @_;
+  my $self = bless ({}, ref ($class) || $class);
+
+  return $self;
+}
+
+my $starttime;
+sub get_start_time{
+  if(!$starttime){
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+    $starttime = sprintf("%04d-%02d-%02d_%02d:%02d:%02d",
+                              $year+1900, $mon+1, $mday, $hour, $min, $sec);
+  }
+  return $starttime;
+}
+
+sub logfilename{
+  return "/var/log/scansetup/scansetup_".get_start_time().".log";
+}
+
+sub get_pm {
+  my ($which_trial) = (@_);
+  $which_trial = $trial unless $which_trial;
+
+  if ($email_target){
+    return [$email_target];
+  }
+
+  my $default;
+
+  open TRIALLIST, "/trials/quarantine/common/lists/email.list"
+    or return $default;
+
+  my %trial_pms;
+  while (<TRIALLIST>) {
+    next if /^\s*(#.*)?$/;
+    chomp;
+    my @line = split /,/;
+    next if @line < 2;
+
+    $trial_pms{$line[0]} = [@line[1..$#line]];
+  }
+  close TRIALLIST;
+
+  open SUBLIST, "/trials/quarantine/common/lists/user_substitutions.list"
+    or return $default;
+
+  while (<SUBLIST>) {
+    next if /^\s*(#.*)?$/;
+    chomp;
+    my @line = split /\./;
+
+    my $from = $line[3];
+    my $to = $line[5];
+    my $until = $line[7];
+
+    my ($sec,$min,$hour,$day,$mon,$year) = localtime(time);
+    my $today = sprintf("%04d-%02d-%02d",$year+1900,$mon+1,$day);
+
+    if ($today lt $until) {
+      foreach my $pms(keys %trial_pms){
+        ## Replace all usages of absent PM with substituting PM
+        map {s/$from/$to/;} @{$trial_pms{$pms}};
+      }
+    }
+  }
+  close SUBLIST;
+
+  if (defined  $trial_pms{$which_trial}){
+    my @pm_emails = @{$trial_pms{$which_trial}};
+
+    foreach my $pm_email(@pm_emails){
+      $pm_email .= '@neurorx.com' unless $pm_email =~ /@/;
+    }
+
+    return \@pm_emails;
+  }
+
+  return $default;
+}
+
+use Cwd qw(getcwd);
+
+sub GetSystemOutput{
+  my ($input, $exit_codes) = @_;
+
+  my @args = split(" ", $input);
+  my $prog = which($args[0]);
+  $logger -> logdie("$args[0]: program not found") unless $prog;
+
+  my ($output, $err, $success, $code) = capture_exec($input);
+
+  if($exit_codes){
+    if(! grep(/$code/, @$exit_codes)){
+      $logger -> error($err);
+      $logger -> logdie("command `$input' failed with exit code $code");
+    }
+  }
+
+  chomp $output;
+
+  return wantarray ? ($output, $err) : $output;
+}
+
+sub RunAndLog{
+  my @args = @_;
+  my $prog = which($args[0]);
+  my ($pkg, $filename, $line) = caller;
+  $logger -> logdie("$args[0]: program not found") unless $prog;
+
+  my $cmd = join(" ", @args);
+  $logger -> debug($cmd);
+  $logger -> debug("Called from: $filename:$line");
+  my ($msg, $err) = capture_exec(@args);
+  $logger -> debug($msg) if $msg;
+  if($err
+     ## This warning is from mincconcat and too frequent to be a warning
+     && !($err =~ m/Don't use an image dimension as a loop dimension/)){
+    $logger -> warn("Problem running $cmd:");
+    $logger -> warn($err);
+  }
+}
+
+sub GetSystemOutputArray {
+
+  my ($InputString,@SysOut);
+
+  $InputString = $_[0];
+
+  open (SYSOUT, "$InputString |");
+  @SysOut = <SYSOUT>;
+  close(SYSOUT);
+  chomp(@SysOut);
+
+  return(@SysOut);
+}
+
+my %mincheader_cache;
+sub ParseHeader {
+  my ($file, $header) = @_;
+
+  my $line;
+  if (! $mincheader_cache{$file}) {
+    $logger -> debug ("Cache miss! Running mincheader on $file");
+    $line = GetSystemOutput("mincheader $file");
+    $mincheader_cache{$file} = $line;
+  }
+  else{
+    $line = $mincheader_cache{$file};
+  }
+  my @out = ($line =~ m/$header = "?([^\n;"]*)"?/);
+  $out[0] =~ s/\s*$// if $out[0];
+  my $out = $out[0] || "NotPresent";
+
+  $out =~ s/\s*$//;
+
+  return $out;
+
+}
+
+sub GetDimensions {
+  my ($file, $dimension) = @_;
+
+  my $line = GetSystemOutput("mincheader $file");
+
+  my @out = ($line =~ m/$dimension = "?([^\n;"]*)"?/);
+  my $out = $out[0] || "";
+
+  $out =~ s/\s*//;
+
+  return $out;
+}
+
+sub GetScanDescription {
+  my ($file) = @_;
+
+  my ($lines, $err, $success, $exit_code) = capture_exec("mincheader", $file);
+  if($err){
+    $logger -> error ($err);
+  }
+  if(! $success){
+    $logger -> logdie ("`mincheader' failed with exit code $exit_code");
+  }
+
+  my @lines = split(/\n/, $lines);
+  @lines = grep(/dicom_0x0008:el_0x103e/, @lines);
+
+  return "NotInHeader" unless @lines;
+
+  $lines[0] =~ m/\s+(.+);/ or return "NotInHeader";
+  my $out = $1;
+  $out =~ s,[/"\s],,g;
+
+  return ($out || "NotInHeader");
+}
+
+sub ParseDicom {
+  use strict;
+  my ($file, $field) = @_;
+
+  $logger -> debug("Looking for field $field in file $file");
+  my ($val, $err) = capture_exec("dicomhdr", $field, $file);
+
+  if($err){
+    $val = "InvalidValue";
+    $logger -> warn("Failed parsing DICOM file:");
+    my @err = split(/\n/, $err);
+    foreach $err(@err){
+      $logger -> warn("\t$err");
+    }
+  }
+
+  $logger -> debug ("Returning value: $val");
+  return($val);
+}
+
+sub GetFileroot {
+  my ($pwd) = @_;
+  my @pwd = split(/\//,$pwd);
+  $logger -> logdie("Not a trial directory: $pwd") if @pwd < 6;
+
+  my $fileroot = $pwd[2]."_".$pwd[3]."_".$pwd[4]."_".$pwd[5];
+  return($fileroot);
+}
+
+sub GetUser {
+  my @outargs = getpwuid($<);
+  my $user = $outargs[6];
+  $user =~ s/\s/_/g;
+  return $user;
+}
+
+sub GetPath {
+    my ($fname) = @_;
+
+    my @f = split(/\//,$fname);
+    $fname = $f[$#f];
+
+    my @fname = split(/_/,$fname);
+
+    my $filepath = "/trials/".$fname[0]."/".$fname[1]."/".$fname[2]."_".$fname[3]."/".$fname[4];
+    return($filepath);
+}
+
+
+sub unlink_glob {
+  my $glob = $_[0];
+  my $cwd = getcwd();
+
+  $logger -> debug("Removing $glob from $cwd...");
+
+  foreach my $file( glob($glob)){
+    unlink($file) or die "Couldn't unlink ${file}: $!";
+    $logger -> debug("Deleted $file");
+  }
+}
+
+#################### main pod documentation begin ###################
+## Below is the stub of documentation for your module. 
+## You better edit it!
+
+
+=head1 NAME
+
+NeuroRx::ScanSetup - Converts incoming DICOM images to MINC
+
+=head1 SYNOPSIS
+
+  use NeuroRx::ScanSetup;
+  blah blah blah
+
+
+=head1 DESCRIPTION
+
+
+
+=head1 USAGE
+
+
+
+=head1 BUGS
+
+
+
+=head1 SUPPORT
+
+
+
+=head1 AUTHOR
+
+    Samson Antel
+    CPAN ID: MODAUTHOR
+    NeuroRx
+    samson@neurorx.com
+
+=head1 COPYRIGHT
+
+This program is free software; you can redistribute
+it and/or modify it under the same terms as Perl itself.
+
+The full text of the license can be found in the
+LICENSE file included with this module.
+
+
+=head1 SEE ALSO
+
+perl(1).
+
+=cut
+
+#################### main pod documentation end ###################
+
+
+1;
+
+
new file mode 100644
--- /dev/null
+++ b/ss/lib/perl5/NeuroRx/ScanSetup/IdentifyModality.pm
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+
+
+sub IdentifyModality{
+}
+
+1;
new file mode 100644
--- /dev/null
+++ b/ss/lib/perl5/NeuroRx/ScanSetup/RenameMincFiles.pm
@@ -0,0 +1,9 @@
+use warnings;
+
+use NeuroRx::ScanSetup qw($logger);
+use Cwd qw(getcwd chdir);
+
+sub RenameMincFiles{
+}
+
+1;
new file mode 100755
--- /dev/null
+++ b/wtf
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+import sh
+import os
+
+lol = sh.Command(os.getcwd() + "/ss/bin/scansetup")
+
+sh.cd("..")
+lol(d = "wtf")
+