diff options
-rw-r--r-- | .i3/config | 4 | ||||
-rw-r--r-- | .i3/scripts/libs/i3.py (renamed from .i3/scripts/i3.py) | 0 | ||||
-rw-r--r-- | .i3/scripts/libs/sh.py | 1695 | ||||
-rwxr-xr-x | .i3/scripts/new_workspace.py | 23 | ||||
-rwxr-xr-x | .i3/scripts/workspaces.py | 74 | ||||
-rwxr-xr-x | .i3/scripts/workspaces.sh | 24 |
6 files changed, 1771 insertions, 49 deletions
@@ -149,14 +149,14 @@ bindsym $mod+Tab workspace back_and_forth bindsym $mod+Shift+Tab move container to workspace back_and_forth # goto a specific workspace, via i3-input «3 -bindsym $mod+g exec $nsi $script/workspaces.sh +bindsym $mod+g exec $nsi $script/workspaces.py switch bindsym $mod+Shift+g exec $nsi $script/workspaces.sh move # rename «3 bindsym $mod+Shift+n exec $nsi i3-input -F 'rename workspace to "%s"' -P 'Rename workspace: ' # new temp workspace «3 -bindsym $mod+n exec $nsi $script/new_workspace.py +bindsym $mod+n exec $nsi $script/workspaces.py new # Resizing «2 ############# diff --git a/.i3/scripts/i3.py b/.i3/scripts/libs/i3.py index 343c709..343c709 100644 --- a/.i3/scripts/i3.py +++ b/.i3/scripts/libs/i3.py diff --git a/.i3/scripts/libs/sh.py b/.i3/scripts/libs/sh.py new file mode 100644 index 0000000..0e46f14 --- /dev/null +++ b/.i3/scripts/libs/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 |