Merge branch '15-advise-tab'

This commit is contained in:
Brian Warner 2017-01-12 16:24:59 -08:00
commit d1c3c621e8
3 changed files with 51 additions and 30 deletions

View File

@ -19,12 +19,14 @@ def extract_channel_id(code):
return channel_id return channel_id
class CodeInputter: class CodeInputter:
def __init__(self, initial_channelids, get_channel_ids, code_length): def __init__(self, initial_channelids, get_channel_ids, code_length,
used_completion_f):
self._initial_channelids = initial_channelids self._initial_channelids = initial_channelids
self._get_channel_ids = get_channel_ids self._get_channel_ids = get_channel_ids
self.code_length = code_length self.code_length = code_length
self.last_text = None # memoize for a speedup self.last_text = None # memoize for a speedup
self.last_matches = None self.last_matches = None
self._used_completion_f = used_completion_f
def get_current_channel_ids(self): def get_current_channel_ids(self):
if self._initial_channelids is not None: if self._initial_channelids is not None:
@ -43,6 +45,7 @@ class CodeInputter:
raise e raise e
def completer(self, text, state): def completer(self, text, state):
self._used_completion_f()
#if state == 0: #if state == 0:
# print("", file=sys.stderr) # print("", file=sys.stderr)
#print("completer: '%s' %d '%d'" % (text, state, #print("completer: '%s' %d '%d'" % (text, state,
@ -84,9 +87,13 @@ class CodeInputter:
def input_code_with_completion(prompt, initial_channelids, get_channel_ids, def input_code_with_completion(prompt, initial_channelids, get_channel_ids,
code_length): code_length):
used_completion = []
def used_completion_f():
used_completion.append(True)
try: try:
import readline import readline
c = CodeInputter(initial_channelids, get_channel_ids, code_length) c = CodeInputter(initial_channelids, get_channel_ids, code_length,
used_completion_f)
if "libedit" in readline.__doc__: if "libedit" in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete") readline.parse_and_bind("bind ^I rl_complete")
else: else:
@ -99,7 +106,7 @@ def input_code_with_completion(prompt, initial_channelids, get_channel_ids,
# Code is str(bytes) on py2, and str(unicode) on py3. We want unicode. # Code is str(bytes) on py2, and str(unicode) on py3. We want unicode.
if isinstance(code, bytes): if isinstance(code, bytes):
code = code.decode("utf-8") code = code.decode("utf-8")
return code return (code, bool(used_completion))
if __name__ == "__main__": if __name__ == "__main__":
code = input_code_with_completion("Enter wormhole code: ", code = input_code_with_completion("Enter wormhole code: ",

View File

@ -1,5 +1,5 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import os, json, re, gc import os, json, re, gc, io
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
import mock import mock
from twisted.trial import unittest from twisted.trial import unittest
@ -105,8 +105,9 @@ class Welcome(unittest.TestCase):
class InputCode(unittest.TestCase): class InputCode(unittest.TestCase):
def test_list(self): def test_list(self):
send_command = mock.Mock() send_command = mock.Mock()
stderr = io.StringIO()
ic = wormhole._InputCode(None, "prompt", 2, send_command, ic = wormhole._InputCode(None, "prompt", 2, send_command,
DebugTiming()) DebugTiming(), stderr)
d = ic._list() d = ic._list()
self.assertNoResult(d) self.assertNoResult(d)
self.assertEqual(send_command.mock_calls, [mock.call("list")]) self.assertEqual(send_command.mock_calls, [mock.call("list")])
@ -114,6 +115,8 @@ class InputCode(unittest.TestCase):
"nameplates": [{"id": "123"}]}) "nameplates": [{"id": "123"}]})
res = self.successResultOf(d) res = self.successResultOf(d)
self.assertEqual(res, ["123"]) self.assertEqual(res, ["123"])
self.assertEqual(stderr.getvalue(), "")
class GetCode(unittest.TestCase): class GetCode(unittest.TestCase):
def test_get(self): def test_get(self):
@ -159,7 +162,7 @@ class Basic(unittest.TestCase):
return key, msg2 return key, msg2
def test_create(self): def test_create(self):
wormhole._Wormhole(APPID, "relay_url", reactor, None, None) wormhole._Wormhole(APPID, "relay_url", reactor, None, None, None)
def test_basic(self): def test_basic(self):
# We don't call w._start(), so this doesn't create a WebSocket # We don't call w._start(), so this doesn't create a WebSocket
@ -172,7 +175,8 @@ class Basic(unittest.TestCase):
timing = DebugTiming() timing = DebugTiming()
with mock.patch("wormhole.wormhole._WelcomeHandler") as wh_c: with mock.patch("wormhole.wormhole._WelcomeHandler") as wh_c:
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing,
None)
wh = wh_c.return_value wh = wh_c.return_value
self.assertEqual(w._ws_url, "relay_url") self.assertEqual(w._ws_url, "relay_url")
self.assertTrue(w._flag_need_nameplate) self.assertTrue(w._flag_need_nameplate)
@ -317,7 +321,7 @@ class Basic(unittest.TestCase):
# Close before the connection is established. The connection still # Close before the connection is established. The connection still
# gets established, but it is then torn down before sending anything. # gets established, but it is then torn down before sending anything.
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
d = w.close() d = w.close()
@ -335,7 +339,7 @@ class Basic(unittest.TestCase):
def test_close_wait_1(self): def test_close_wait_1(self):
# close before even claiming the nameplate # close before even claiming the nameplate
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
@ -354,7 +358,7 @@ class Basic(unittest.TestCase):
# Close after claiming the nameplate, but before opening the mailbox. # Close after claiming the nameplate, but before opening the mailbox.
# The 'claimed' response arrives before we close. # The 'claimed' response arrives before we close.
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
@ -385,7 +389,7 @@ class Basic(unittest.TestCase):
# close after claiming the nameplate, but before opening the mailbox # close after claiming the nameplate, but before opening the mailbox
# The 'claimed' response arrives after we start to close. # The 'claimed' response arrives after we start to close.
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
@ -410,7 +414,7 @@ class Basic(unittest.TestCase):
def test_close_wait_4(self): def test_close_wait_4(self):
# close after both claiming the nameplate and opening the mailbox # close after both claiming the nameplate and opening the mailbox
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
@ -440,7 +444,7 @@ class Basic(unittest.TestCase):
# close after claiming the nameplate, opening the mailbox, then # close after claiming the nameplate, opening the mailbox, then
# releasing the nameplate # releasing the nameplate
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
@ -481,7 +485,7 @@ class Basic(unittest.TestCase):
def test_get_code_mock(self): def test_get_code_mock(self):
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
ws = MockWebSocket() # TODO: mock w._ws_send_command instead ws = MockWebSocket() # TODO: mock w._ws_send_command instead
w._event_connected(ws) w._event_connected(ws)
w._event_ws_opened(None) w._event_ws_opened(None)
@ -500,7 +504,7 @@ class Basic(unittest.TestCase):
def test_get_code_real(self): def test_get_code_real(self):
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
w._event_ws_opened(None) w._event_ws_opened(None)
@ -524,7 +528,7 @@ class Basic(unittest.TestCase):
def _test_establish_key_hook(self, established, before): def _test_establish_key_hook(self, established, before):
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
if before: if before:
d = w.establish_key() d = w.establish_key()
@ -556,7 +560,7 @@ class Basic(unittest.TestCase):
def test_establish_key_twice(self): def test_establish_key_twice(self):
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
d = w.establish_key() d = w.establish_key()
self.assertRaises(InternalError, w.establish_key) self.assertRaises(InternalError, w.establish_key)
del d del d
@ -571,7 +575,7 @@ class Basic(unittest.TestCase):
#print(when, order, success) #print(when, order, success)
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
w._ws_send_command = mock.Mock() w._ws_send_command = mock.Mock()
w._mailbox_state = wormhole.OPEN w._mailbox_state = wormhole.OPEN
@ -634,7 +638,7 @@ class Basic(unittest.TestCase):
# states in which we might see it. # states in which we might see it.
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)
@ -667,7 +671,7 @@ class Basic(unittest.TestCase):
# PAKE message, by which point we should know the key. If the # PAKE message, by which point we should know the key. If the
# confirmation message doesn't decrypt, we signal an error. # confirmation message doesn't decrypt, we signal an error.
timing = DebugTiming() timing = DebugTiming()
w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing, None)
w._drop_connection = mock.Mock() w._drop_connection = mock.Mock()
ws = MockWebSocket() ws = MockWebSocket()
w._event_connected(ws) w._event_connected(ws)

View File

@ -89,12 +89,14 @@ class _GetCode:
self._allocated_d.callback(nid) self._allocated_d.callback(nid)
class _InputCode: class _InputCode:
def __init__(self, reactor, prompt, code_length, send_command, timing): def __init__(self, reactor, prompt, code_length, send_command, timing,
stderr):
self._reactor = reactor self._reactor = reactor
self._prompt = prompt self._prompt = prompt
self._code_length = code_length self._code_length = code_length
self._send_command = send_command self._send_command = send_command
self._timing = timing self._timing = timing
self._stderr = stderr
@inlineCallbacks @inlineCallbacks
def _list(self): def _list(self):
@ -121,12 +123,15 @@ class _InputCode:
with self._timing.add("input code", waiting="user"): with self._timing.add("input code", waiting="user"):
t = self._reactor.addSystemEventTrigger("before", "shutdown", t = self._reactor.addSystemEventTrigger("before", "shutdown",
self._warn_readline) self._warn_readline)
code = yield deferToThread(codes.input_code_with_completion, res = yield deferToThread(codes.input_code_with_completion,
self._prompt, self._prompt,
initial_nameplate_ids, initial_nameplate_ids,
self._list_blocking, self._list_blocking,
self._code_length) self._code_length)
(code, used_completion) = res
self._reactor.removeSystemEventTrigger(t) self._reactor.removeSystemEventTrigger(t)
if not used_completion:
self._remind_about_tab()
returnValue(code) returnValue(code)
def _response_handle_nameplates(self, msg): def _response_handle_nameplates(self, msg):
@ -174,6 +179,9 @@ class _InputCode:
# doesn't see the signal, and we must still wait for stdin to make # doesn't see the signal, and we must still wait for stdin to make
# readline finish. # readline finish.
def _remind_about_tab(self):
print(" (note: you can use <Tab> to complete words)", file=self._stderr)
class _WelcomeHandler: class _WelcomeHandler:
def __init__(self, url, current_version, signal_error): def __init__(self, url, current_version, signal_error):
self._ws_url = url self._ws_url = url
@ -211,12 +219,13 @@ class _WelcomeHandler:
class _Wormhole: class _Wormhole:
DEBUG = False DEBUG = False
def __init__(self, appid, relay_url, reactor, tor_manager, timing): def __init__(self, appid, relay_url, reactor, tor_manager, timing, stderr):
self._appid = appid self._appid = appid
self._ws_url = relay_url self._ws_url = relay_url
self._reactor = reactor self._reactor = reactor
self._tor_manager = tor_manager self._tor_manager = tor_manager
self._timing = timing self._timing = timing
self._stderr = stderr
self._welcomer = _WelcomeHandler(self._ws_url, __version__, self._welcomer = _WelcomeHandler(self._ws_url, __version__,
self._signal_error) self._signal_error)
@ -460,7 +469,7 @@ class _Wormhole:
with self._timing.add("API input_code"): with self._timing.add("API input_code"):
yield self._when_connected() yield self._when_connected()
ic = _InputCode(self._reactor, prompt, code_length, ic = _InputCode(self._reactor, prompt, code_length,
self._ws_send_command, self._timing) self._ws_send_command, self._timing, self._stderr)
self._response_handle_nameplates = ic._response_handle_nameplates self._response_handle_nameplates = ic._response_handle_nameplates
# we reveal the Deferred we're waiting on, so _signal_error can # we reveal the Deferred we're waiting on, so _signal_error can
# wake us up if something goes wrong (like a welcome error) # wake us up if something goes wrong (like a welcome error)
@ -927,9 +936,10 @@ class _Wormhole:
# * can't re-close websocket # * can't re-close websocket
# * close(wait=True) callers should fire right away # * close(wait=True) callers should fire right away
def wormhole(appid, relay_url, reactor, tor_manager=None, timing=None): def wormhole(appid, relay_url, reactor, tor_manager=None, timing=None,
stderr=sys.stderr):
timing = timing or DebugTiming() timing = timing or DebugTiming()
w = _Wormhole(appid, relay_url, reactor, tor_manager, timing) w = _Wormhole(appid, relay_url, reactor, tor_manager, timing, stderr)
w._start() w._start()
return w return w