2017-03-12 17:38:48 +00:00
|
|
|
from __future__ import print_function, absolute_import, unicode_literals
|
|
|
|
from zope.interface import implementer
|
|
|
|
from attr import attrs, attrib
|
|
|
|
from attr.validators import provides
|
2017-04-06 01:26:28 +00:00
|
|
|
from twisted.internet import defer
|
2017-03-12 17:38:48 +00:00
|
|
|
from automat import MethodicalMachine
|
2017-03-19 16:35:05 +00:00
|
|
|
from . import _interfaces, errors
|
|
|
|
|
|
|
|
def first(outputs):
|
|
|
|
return list(outputs)[0]
|
2017-03-12 17:38:48 +00:00
|
|
|
|
|
|
|
@attrs
|
|
|
|
@implementer(_interfaces.IInput)
|
|
|
|
class Input(object):
|
|
|
|
_timing = attrib(validator=provides(_interfaces.ITiming))
|
|
|
|
m = MethodicalMachine()
|
2017-04-13 20:34:22 +00:00
|
|
|
set_trace = getattr(m, "_setTrace", lambda self, f: None)
|
2017-03-12 17:38:48 +00:00
|
|
|
|
|
|
|
def __attrs_post_init__(self):
|
2017-03-19 16:35:05 +00:00
|
|
|
self._all_nameplates = set()
|
2017-03-12 17:38:48 +00:00
|
|
|
self._nameplate = None
|
|
|
|
self._wordlist = None
|
2017-04-06 01:26:28 +00:00
|
|
|
self._wordlist_waiters = []
|
2017-03-12 17:38:48 +00:00
|
|
|
|
|
|
|
def wire(self, code, lister):
|
|
|
|
self._C = _interfaces.ICode(code)
|
|
|
|
self._L = _interfaces.ILister(lister)
|
|
|
|
|
2017-04-06 01:26:28 +00:00
|
|
|
def when_wordlist_is_available(self):
|
|
|
|
if self._wordlist:
|
|
|
|
return defer.succeed(None)
|
|
|
|
d = defer.Deferred()
|
|
|
|
self._wordlist_waiters.append(d)
|
|
|
|
return d
|
|
|
|
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.state(initial=True)
|
|
|
|
def S0_idle(self): pass # pragma: no cover
|
|
|
|
@m.state()
|
2017-03-17 23:50:37 +00:00
|
|
|
def S1_typing_nameplate(self): pass # pragma: no cover
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.state()
|
2017-03-17 23:50:37 +00:00
|
|
|
def S2_typing_code_no_wordlist(self): pass # pragma: no cover
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.state()
|
2017-03-17 23:50:37 +00:00
|
|
|
def S3_typing_code_yes_wordlist(self): pass # pragma: no cover
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.state(terminal=True)
|
|
|
|
def S4_done(self): pass # pragma: no cover
|
|
|
|
|
|
|
|
# from Code
|
|
|
|
@m.input()
|
2017-03-19 16:35:05 +00:00
|
|
|
def start(self): pass
|
2017-03-12 17:38:48 +00:00
|
|
|
|
|
|
|
# from Lister
|
|
|
|
@m.input()
|
2017-03-19 16:35:05 +00:00
|
|
|
def got_nameplates(self, all_nameplates): pass
|
2017-03-12 17:38:48 +00:00
|
|
|
|
2017-03-17 23:50:37 +00:00
|
|
|
# from Nameplate
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.input()
|
|
|
|
def got_wordlist(self, wordlist): pass
|
|
|
|
|
2017-03-15 07:43:25 +00:00
|
|
|
# API provided to app as ICodeInputHelper
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.input()
|
|
|
|
def refresh_nameplates(self): pass
|
|
|
|
@m.input()
|
2017-03-19 16:35:05 +00:00
|
|
|
def get_nameplate_completions(self, prefix): pass
|
|
|
|
@m.input()
|
2017-03-17 23:50:37 +00:00
|
|
|
def choose_nameplate(self, nameplate): pass
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.input()
|
2017-03-19 16:35:05 +00:00
|
|
|
def get_word_completions(self, prefix): pass
|
|
|
|
@m.input()
|
2017-03-12 17:38:48 +00:00
|
|
|
def choose_words(self, words): pass
|
|
|
|
|
|
|
|
@m.output()
|
2017-03-19 16:35:05 +00:00
|
|
|
def do_start(self):
|
2017-04-18 00:13:14 +00:00
|
|
|
self._start_timing = self._timing.add("input code", waiting="user")
|
2017-03-19 16:35:05 +00:00
|
|
|
self._L.refresh()
|
|
|
|
return Helper(self)
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.output()
|
2017-03-17 23:50:37 +00:00
|
|
|
def do_refresh(self):
|
2017-03-19 16:35:05 +00:00
|
|
|
self._L.refresh()
|
|
|
|
@m.output()
|
|
|
|
def record_nameplates(self, all_nameplates):
|
|
|
|
# we get a set of nameplate id strings
|
|
|
|
self._all_nameplates = all_nameplates
|
|
|
|
@m.output()
|
|
|
|
def _get_nameplate_completions(self, prefix):
|
|
|
|
completions = set()
|
|
|
|
for nameplate in self._all_nameplates:
|
|
|
|
if nameplate.startswith(prefix):
|
2017-04-06 01:26:28 +00:00
|
|
|
# TODO: it's a little weird that Input is responsible for the
|
|
|
|
# hyphen on nameplates, but WordList owns it for words
|
|
|
|
completions.add(nameplate+"-")
|
2017-03-19 16:35:05 +00:00
|
|
|
return completions
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.output()
|
2017-03-19 16:35:05 +00:00
|
|
|
def record_all_nameplates(self, nameplate):
|
2017-03-12 17:38:48 +00:00
|
|
|
self._nameplate = nameplate
|
|
|
|
self._C.got_nameplate(nameplate)
|
2017-03-17 23:50:37 +00:00
|
|
|
@m.output()
|
2017-03-19 16:35:05 +00:00
|
|
|
def record_wordlist(self, wordlist):
|
2017-03-22 23:10:02 +00:00
|
|
|
from ._rlcompleter import debug
|
|
|
|
debug(" -record_wordlist")
|
2017-03-17 23:50:37 +00:00
|
|
|
self._wordlist = wordlist
|
2017-04-06 01:26:28 +00:00
|
|
|
@m.output()
|
|
|
|
def notify_wordlist_waiters(self, wordlist):
|
|
|
|
while self._wordlist_waiters:
|
|
|
|
d = self._wordlist_waiters.pop()
|
|
|
|
d.callback(None)
|
2017-03-12 17:38:48 +00:00
|
|
|
|
2017-03-19 16:35:05 +00:00
|
|
|
@m.output()
|
|
|
|
def no_word_completions(self, prefix):
|
|
|
|
return set()
|
|
|
|
@m.output()
|
|
|
|
def _get_word_completions(self, prefix):
|
|
|
|
assert self._wordlist
|
|
|
|
return self._wordlist.get_completions(prefix)
|
|
|
|
|
|
|
|
@m.output()
|
|
|
|
def raise_must_choose_nameplate1(self, prefix):
|
|
|
|
raise errors.MustChooseNameplateFirstError()
|
|
|
|
@m.output()
|
|
|
|
def raise_must_choose_nameplate2(self, words):
|
|
|
|
raise errors.MustChooseNameplateFirstError()
|
|
|
|
@m.output()
|
|
|
|
def raise_already_chose_nameplate1(self):
|
|
|
|
raise errors.AlreadyChoseNameplateError()
|
|
|
|
@m.output()
|
|
|
|
def raise_already_chose_nameplate2(self, prefix):
|
|
|
|
raise errors.AlreadyChoseNameplateError()
|
|
|
|
@m.output()
|
|
|
|
def raise_already_chose_nameplate3(self, nameplate):
|
|
|
|
raise errors.AlreadyChoseNameplateError()
|
|
|
|
@m.output()
|
|
|
|
def raise_already_chose_words1(self, prefix):
|
|
|
|
raise errors.AlreadyChoseWordsError()
|
|
|
|
@m.output()
|
|
|
|
def raise_already_chose_words2(self, words):
|
|
|
|
raise errors.AlreadyChoseWordsError()
|
|
|
|
|
2017-03-12 17:38:48 +00:00
|
|
|
@m.output()
|
2017-03-17 23:50:37 +00:00
|
|
|
def do_words(self, words):
|
2017-03-12 17:38:48 +00:00
|
|
|
code = self._nameplate + "-" + words
|
2017-04-18 00:13:14 +00:00
|
|
|
self._start_timing.finish()
|
2017-03-12 17:38:48 +00:00
|
|
|
self._C.finished_input(code)
|
|
|
|
|
2017-03-19 16:35:05 +00:00
|
|
|
S0_idle.upon(start, enter=S1_typing_nameplate,
|
|
|
|
outputs=[do_start], collector=first)
|
2017-03-19 22:23:25 +00:00
|
|
|
# wormholes that don't use input_code (i.e. they use allocate_code or
|
|
|
|
# generate_code) will never start() us, but Nameplate will give us a
|
|
|
|
# wordlist anyways (as soon as the nameplate is claimed), so handle it.
|
2017-04-06 01:26:28 +00:00
|
|
|
S0_idle.upon(got_wordlist, enter=S0_idle, outputs=[record_wordlist,
|
|
|
|
notify_wordlist_waiters])
|
2017-03-19 16:35:05 +00:00
|
|
|
S1_typing_nameplate.upon(got_nameplates, enter=S1_typing_nameplate,
|
|
|
|
outputs=[record_nameplates])
|
2017-03-19 22:23:25 +00:00
|
|
|
# but wormholes that *do* use input_code should not get got_wordlist
|
|
|
|
# until after we tell Code that we got_nameplate, which is the earliest
|
|
|
|
# it can be claimed
|
2017-03-17 23:50:37 +00:00
|
|
|
S1_typing_nameplate.upon(refresh_nameplates, enter=S1_typing_nameplate,
|
|
|
|
outputs=[do_refresh])
|
2017-03-19 16:35:05 +00:00
|
|
|
S1_typing_nameplate.upon(get_nameplate_completions,
|
|
|
|
enter=S1_typing_nameplate,
|
|
|
|
outputs=[_get_nameplate_completions],
|
|
|
|
collector=first)
|
2017-03-17 23:50:37 +00:00
|
|
|
S1_typing_nameplate.upon(choose_nameplate, enter=S2_typing_code_no_wordlist,
|
2017-03-19 16:35:05 +00:00
|
|
|
outputs=[record_all_nameplates])
|
|
|
|
S1_typing_nameplate.upon(get_word_completions,
|
|
|
|
enter=S1_typing_nameplate,
|
|
|
|
outputs=[raise_must_choose_nameplate1])
|
|
|
|
S1_typing_nameplate.upon(choose_words, enter=S1_typing_nameplate,
|
|
|
|
outputs=[raise_must_choose_nameplate2])
|
|
|
|
|
|
|
|
S2_typing_code_no_wordlist.upon(got_nameplates,
|
|
|
|
enter=S2_typing_code_no_wordlist, outputs=[])
|
2017-03-17 23:50:37 +00:00
|
|
|
S2_typing_code_no_wordlist.upon(got_wordlist,
|
|
|
|
enter=S3_typing_code_yes_wordlist,
|
2017-04-06 01:26:28 +00:00
|
|
|
outputs=[record_wordlist,
|
|
|
|
notify_wordlist_waiters])
|
2017-03-19 16:35:05 +00:00
|
|
|
S2_typing_code_no_wordlist.upon(refresh_nameplates,
|
|
|
|
enter=S2_typing_code_no_wordlist,
|
|
|
|
outputs=[raise_already_chose_nameplate1])
|
|
|
|
S2_typing_code_no_wordlist.upon(get_nameplate_completions,
|
|
|
|
enter=S2_typing_code_no_wordlist,
|
|
|
|
outputs=[raise_already_chose_nameplate2])
|
|
|
|
S2_typing_code_no_wordlist.upon(choose_nameplate,
|
|
|
|
enter=S2_typing_code_no_wordlist,
|
|
|
|
outputs=[raise_already_chose_nameplate3])
|
|
|
|
S2_typing_code_no_wordlist.upon(get_word_completions,
|
|
|
|
enter=S2_typing_code_no_wordlist,
|
|
|
|
outputs=[no_word_completions],
|
|
|
|
collector=first)
|
2017-03-17 23:50:37 +00:00
|
|
|
S2_typing_code_no_wordlist.upon(choose_words, enter=S4_done,
|
|
|
|
outputs=[do_words])
|
2017-03-19 16:35:05 +00:00
|
|
|
|
2017-03-17 23:50:37 +00:00
|
|
|
S3_typing_code_yes_wordlist.upon(got_nameplates,
|
|
|
|
enter=S3_typing_code_yes_wordlist,
|
|
|
|
outputs=[])
|
2017-03-19 16:35:05 +00:00
|
|
|
# got_wordlist: should never happen
|
|
|
|
S3_typing_code_yes_wordlist.upon(refresh_nameplates,
|
|
|
|
enter=S3_typing_code_yes_wordlist,
|
|
|
|
outputs=[raise_already_chose_nameplate1])
|
|
|
|
S3_typing_code_yes_wordlist.upon(get_nameplate_completions,
|
|
|
|
enter=S3_typing_code_yes_wordlist,
|
|
|
|
outputs=[raise_already_chose_nameplate2])
|
|
|
|
S3_typing_code_yes_wordlist.upon(choose_nameplate,
|
|
|
|
enter=S3_typing_code_yes_wordlist,
|
|
|
|
outputs=[raise_already_chose_nameplate3])
|
|
|
|
S3_typing_code_yes_wordlist.upon(get_word_completions,
|
|
|
|
enter=S3_typing_code_yes_wordlist,
|
|
|
|
outputs=[_get_word_completions],
|
|
|
|
collector=first)
|
|
|
|
S3_typing_code_yes_wordlist.upon(choose_words, enter=S4_done,
|
|
|
|
outputs=[do_words])
|
|
|
|
|
2017-03-17 23:50:37 +00:00
|
|
|
S4_done.upon(got_nameplates, enter=S4_done, outputs=[])
|
|
|
|
S4_done.upon(got_wordlist, enter=S4_done, outputs=[])
|
2017-03-19 16:35:05 +00:00
|
|
|
S4_done.upon(refresh_nameplates,
|
|
|
|
enter=S4_done,
|
|
|
|
outputs=[raise_already_chose_nameplate1])
|
|
|
|
S4_done.upon(get_nameplate_completions,
|
|
|
|
enter=S4_done,
|
|
|
|
outputs=[raise_already_chose_nameplate2])
|
|
|
|
S4_done.upon(choose_nameplate, enter=S4_done,
|
|
|
|
outputs=[raise_already_chose_nameplate3])
|
|
|
|
S4_done.upon(get_word_completions, enter=S4_done,
|
|
|
|
outputs=[raise_already_chose_words1])
|
|
|
|
S4_done.upon(choose_words, enter=S4_done,
|
|
|
|
outputs=[raise_already_chose_words2])
|
2017-03-12 17:38:48 +00:00
|
|
|
|
2017-03-19 16:35:05 +00:00
|
|
|
# we only expose the Helper to application code, not _Input
|
|
|
|
@attrs
|
|
|
|
class Helper(object):
|
|
|
|
_input = attrib()
|
2017-03-12 17:38:48 +00:00
|
|
|
|
2017-03-19 16:35:05 +00:00
|
|
|
def refresh_nameplates(self):
|
|
|
|
self._input.refresh_nameplates()
|
2017-03-12 17:38:48 +00:00
|
|
|
def get_nameplate_completions(self, prefix):
|
2017-03-19 16:35:05 +00:00
|
|
|
return self._input.get_nameplate_completions(prefix)
|
|
|
|
def choose_nameplate(self, nameplate):
|
|
|
|
self._input.choose_nameplate(nameplate)
|
2017-04-06 01:26:28 +00:00
|
|
|
def when_wordlist_is_available(self):
|
|
|
|
return self._input.when_wordlist_is_available()
|
2017-03-12 17:38:48 +00:00
|
|
|
def get_word_completions(self, prefix):
|
2017-03-19 16:35:05 +00:00
|
|
|
return self._input.get_word_completions(prefix)
|
|
|
|
def choose_words(self, words):
|
|
|
|
self._input.choose_words(words)
|