Skip to content

Commit

Permalink
input prompts are now cleaned up automatically.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismeyers committed Feb 17, 2019
1 parent df7d88c commit 180c393
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 14 deletions.
15 changes: 12 additions & 3 deletions examples/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
print('before')
before = time.time()

with pleasehold.hold('starting', 'complete') as holding:
with pleasehold.hold('starting', 'complete', symbol='#') as holding:
time.sleep(2)
with pleasehold.transfer(holding) as t:
stdin1 = t.input('enter a push notification: ')
stdin2 = t.input('enter another push notification: ')
stdin1 = input('enter a push notification: ')
stdin2 = input('enter another push notification: ')
stdin3 = input('enter a third push notification: ')
holding.push(stdin1)
holding.push(stdin2)
holding.push(stdin3)

time.sleep(2)
with pleasehold.transfer(holding) as t:
input('this won\'t be pushed: ')
time.sleep(2)

# NOTE: The sudo password prompt interacts directly to /dev/tty, so it
# won't be picked up through StreamTee and cleaned up automatically.

print(f'time elapsed: {time.time() - before}')
16 changes: 9 additions & 7 deletions pleasehold/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import sys
import threading
import time
import io
from . import stream_tee
from . import terminal as term


Expand Down Expand Up @@ -62,7 +64,7 @@ def loading_event(self):
def start(self, msg=None):
self._begin_msg = msg if msg is not None else self._begin_msg

print(self._begin_msg, end='', flush=True)
print(f'{self._begin_msg}{self._loading_ticks}', end='', flush=True)

self._loading_event.set()

Expand Down Expand Up @@ -101,23 +103,23 @@ def _loading(self):
class Transfer():
def __init__(self, holding):
self._holding = holding
self._num_inputs = 0
self._output_stream = io.StringIO()
self._stream_tee = stream_tee.StreamTee(
sys.stdout, self._output_stream, holding.symbol)

def __enter__(self):
sys.stdout = self._stream_tee
term.move_line_down()
self._holding.loading_event.clear()
return self

def __exit__(self, type, value, traceback):
for _ in range(self._num_inputs + 1):
sys.stdout = sys.__stdout__
for _ in range(self._stream_tee.num_inputs + 1):
term.clear_line()
term.move_line_up()
self._holding.start()

def input(self, msg):
self._num_inputs += 1
return input(msg)


def hold(begin_msg='begin', end_msg='end', delay=1.0, symbol='.'):
return PleaseHold(begin_msg, end_msg, delay, symbol)
Expand Down
37 changes: 37 additions & 0 deletions pleasehold/stream_tee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'''
This class forks a stream, allowing you to capture output while still displaying
output in the terminal.
See: http://www.tentech.ca/2011/05/stream-tee-in-python-saving-stdout-to-file-while-keeping-the-console-alive/
'''
class StreamTee(object):
# Based on https://gist.github.com/327585 by Anand Kunal
def __init__(self, stream1, stream2, symbol='.'):
self._stream1 = stream1
self._stream2 = stream2
self.__missing_method_name = None # Hack!
self._loading_symbol = symbol
self._num_inputs = 0

def __getattribute__(self, name):
return object.__getattribute__(self, name)

def __getattr__(self, name):
self.__missing_method_name = name # Could also be a property
return getattr(self, '__methodmissing__')

def __methodmissing__(self, *args, **kwargs):
if len(args) > 0 and args[0] != '\n' and args[0] != self._loading_symbol:
self._num_inputs += 1

# Emit method call to the forked stream
callable2 = getattr(self._stream2, self.__missing_method_name)
callable2(*args, **kwargs)

# Emit method call to stream 1
callable1 = getattr(self._stream1, self.__missing_method_name)
return callable1(*args, **kwargs)

@property
def num_inputs(self):
return self._num_inputs
8 changes: 4 additions & 4 deletions tests/unit/test_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pleasehold


def test_push_from_input(capsys, monkeypatch):
def test_push_from_input(capfd, monkeypatch):
duration = 2
input_msg = 'mock'

Expand All @@ -16,7 +16,7 @@ def test_push_from_input(capsys, monkeypatch):
stdin = input(input_msg)
holding.push(stdin)
time.sleep(duration)
captured = capsys.readouterr()
captured = capfd.readouterr()

# The first line of stdout contains the begin msg and loading ticks
# The second line of stdout contains the clear line escape sequence
Expand All @@ -25,7 +25,7 @@ def test_push_from_input(capsys, monkeypatch):
assert stdout[2] == input_msg


def test_symbols_after_input(capsys, monkeypatch):
def test_symbols_after_input(capfd, monkeypatch):
duration = 2
sleeps = 0
input_msg = 'mock'
Expand All @@ -44,7 +44,7 @@ def test_symbols_after_input(capsys, monkeypatch):
holding.push(stdin)
time.sleep(duration)
sleeps += 1
captured = capsys.readouterr()
captured = capfd.readouterr()

stdout = captured.out.strip().split('\n')
c = Counter(stdout[-1])
Expand Down

0 comments on commit 180c393

Please sign in to comment.