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.
112 lines
3.0 KiB
112 lines
3.0 KiB
2 years ago
|
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
|