You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
3.0 KiB
111 lines
3.0 KiB
from __future__ import print_function |
|
|
|
""" |
|
Utility class for spawning and controlling CLI processes. |
|
See: http://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python |
|
""" |
|
import sys, time |
|
from subprocess import PIPE, Popen |
|
from threading import Thread |
|
|
|
try: |
|
from Queue import Queue, Empty |
|
except ImportError: |
|
from queue import Queue, Empty # python 3.x |
|
|
|
ON_POSIX = "posix" in sys.builtin_module_names |
|
|
|
|
|
class Process(object): |
|
""" |
|
Process encapsulates a subprocess with queued stderr/stdout pipes. |
|
Wraps most methods from subprocess.Popen. |
|
|
|
For non-blocking reads, use proc.stdout.get_nowait() or |
|
proc.stdout.get(timeout=0.1). |
|
""" |
|
|
|
def __init__(self, exec_args, cwd=None): |
|
self.proc = Popen( |
|
exec_args, |
|
stdout=PIPE, |
|
stdin=PIPE, |
|
stderr=PIPE, |
|
bufsize=1, |
|
close_fds=ON_POSIX, |
|
universal_newlines=True, |
|
cwd=cwd, |
|
) |
|
self.stdin = self.proc.stdin |
|
# self.stdin = PipePrinter(self.proc.stdin) |
|
self.stdout = PipeQueue(self.proc.stdout) |
|
self.stderr = PipeQueue(self.proc.stderr) |
|
for method in ["poll", "wait", "send_signal", "kill", "terminate"]: |
|
setattr(self, method, getattr(self.proc, method)) |
|
|
|
|
|
class PipePrinter(object): |
|
""" For debugging writes to a pipe. |
|
""" |
|
|
|
def __init__(self, pipe): |
|
self._pipe = pipe |
|
|
|
def __getattr__(self, attr): |
|
return getattr(self._pipe, attr) |
|
|
|
def write(self, strn): |
|
print("WRITE:" + repr(strn)) |
|
return self._pipe.write(strn) |
|
|
|
|
|
class PipeQueue(Queue): |
|
""" |
|
Queue that starts a second process to monitor a PIPE for new data. |
|
This is needed to allow non-blocking pipe reads. |
|
""" |
|
|
|
def __init__(self, pipe): |
|
Queue.__init__(self) |
|
self.thread = Thread(target=self.enqueue_output, args=(pipe, self)) |
|
self.thread.daemon = True # thread dies with the program |
|
self.thread.start() |
|
|
|
@staticmethod |
|
def enqueue_output(out, queue): |
|
for line in iter(out.readline, b""): |
|
queue.put(line) |
|
# print "READ: " + repr(line) |
|
out.close() |
|
|
|
def read(self): |
|
""" |
|
Read all available lines from the queue, concatenated into a single string. |
|
""" |
|
out = "" |
|
while True: |
|
try: |
|
out += self.get_nowait() |
|
except Empty: |
|
break |
|
return out |
|
|
|
def readline(self, timeout=None): |
|
""" Read a single line from the queue. |
|
""" |
|
# we break this up into multiple short reads to allow keyboard |
|
# interrupts |
|
start = time.time() |
|
ret = "" |
|
while True: |
|
if timeout is not None: |
|
remaining = start + timeout - time.time() |
|
if remaining <= 0: |
|
return "" |
|
else: |
|
remaining = 1 |
|
|
|
try: |
|
return self.get(timeout=min(0.1, remaining)) |
|
except Empty: |
|
pass
|
|
|