예제 #1
0
    def test_dictionary_collection_longest_key(self):

        k1 = ('S', )
        k2 = ('S', 'T')
        k3 = ('S', 'T', 'R')

        dc = StenoDictionaryCollection()
        self.assertEqual(dc.longest_key, 0)

        d1 = StenoDictionary()
        d1._path = 'd1'
        d1.save = lambda: None
        d1[k1] = 'a'

        dc.set_dicts([d1])
        self.assertEqual(dc.longest_key, 1)

        d1[k2] = 'a'
        self.assertEqual(dc.longest_key, 2)

        d2 = StenoDictionary()
        d2._path = 'd2'
        d2[k3] = 'c'

        dc.set_dicts([d1, d2])
        self.assertEqual(dc.longest_key, 3)

        del d1[k2]
        self.assertEqual(dc.longest_key, 3)

        dc.set_dicts([d1])
        self.assertEqual(dc.longest_key, 1)

        dc.set_dicts([])
        self.assertEqual(dc.longest_key, 0)
예제 #2
0
class TranslatorStateSizeTestCase(unittest.TestCase):
    class FakeState(_State):
        def __init__(self):
            _State.__init__(self)
            self.restrict_calls = []

        def restrict_size(self, n):
            self.restrict_calls.append(n)

    def assert_size_call(self, size):
        self.assertEqual(self.s.restrict_calls[-1], size)

    def assert_no_size_call(self):
        self.assertEqual(self.s.restrict_calls, [])

    def clear(self):
        self.s.restrict_calls = []

    def setUp(self):
        self.t = Translator()
        self.s = type(self).FakeState()
        self.t._state = self.s
        self.d = StenoDictionary()
        self.dc = StenoDictionaryCollection()
        self.dc.set_dicts([self.d])
        self.t.set_dictionary(self.dc)

    def test_dictionary_update_grows_size1(self):
        self.d[('S', )] = '1'
        self.assert_size_call(1)

    def test_dictionary_update_grows_size4(self):
        self.d[('S', 'PT', '-Z', 'TOP')] = 'hi'
        self.assert_size_call(4)

    def test_dictionary_update_no_grow(self):
        self.t.set_min_undo_length(4)
        self.assert_size_call(4)
        self.clear()
        self.d[('S', 'T')] = 'nothing'
        self.assert_size_call(4)

    def test_dictionary_update_shrink(self):
        self.d[('S', 'T', 'P', '-Z', '-D')] = '1'
        self.assert_size_call(5)
        self.clear()
        self.d[('A', 'P')] = '2'
        self.assert_no_size_call()
        del self.d[('S', 'T', 'P', '-Z', '-D')]
        self.assert_size_call(2)

    def test_dictionary_update_no_shrink(self):
        self.t.set_min_undo_length(7)
        self.d[('S', 'T', 'P', '-Z', '-D')] = '1'
        del self.d[('S', 'T', 'P', '-Z', '-D')]
        self.assert_size_call(7)

    def test_translation_calls_restrict(self):
        self.t.translate(stroke('S'))
        self.assert_size_call(0)
예제 #3
0
def test_dictionary_collection_longest_key():

    k1 = ('S',)
    k2 = ('S', 'T')
    k3 = ('S', 'T', 'R')

    dc = StenoDictionaryCollection()
    assert dc.longest_key == 0

    d1 = StenoDictionary()
    d1.path = 'd1'
    d1[k1] = 'a'

    dc.set_dicts([d1])
    assert dc.longest_key == 1

    d1[k2] = 'a'
    assert dc.longest_key == 2

    d2 = StenoDictionary()
    d2.path = 'd2'
    d2[k3] = 'c'

    dc.set_dicts([d2, d1])
    assert dc.longest_key == 3

    del d1[k2]
    assert dc.longest_key == 3

    dc.set_dicts([d1])
    assert dc.longest_key == 1

    dc.set_dicts([])
    assert dc.longest_key == 0
예제 #4
0
class TranslatorStateSizeTestCase(unittest.TestCase):
    class FakeState(_State):
        def __init__(self):
            _State.__init__(self)
            self.restrict_calls = []
        def restrict_size(self, n):
            self.restrict_calls.append(n)

    def assert_size_call(self, size):
        self.assertEqual(self.s.restrict_calls[-1], size)

    def assert_no_size_call(self):
        self.assertEqual(self.s.restrict_calls, [])

    def clear(self):
        self.s.restrict_calls = []

    def setUp(self):
        self.t = Translator()
        self.s = type(self).FakeState()
        self.t._state = self.s
        self.d = StenoDictionary()
        self.dc = StenoDictionaryCollection()
        self.dc.set_dicts([self.d])
        self.t.set_dictionary(self.dc)

    def test_dictionary_update_grows_size1(self):
        self.d[('S',)] = '1'
        self.assert_size_call(1)

    def test_dictionary_update_grows_size4(self):
        self.d[('S', 'PT', '-Z', 'TOP')] = 'hi'
        self.assert_size_call(4)

    def test_dictionary_update_no_grow(self):
        self.t.set_min_undo_length(4)
        self.assert_size_call(4)
        self.clear()
        self.d[('S', 'T')] = 'nothing'
        self.assert_size_call(4)

    def test_dictionary_update_shrink(self):
        self.d[('S', 'T', 'P', '-Z', '-D')] = '1'
        self.assert_size_call(5)
        self.clear()
        self.d[('A', 'P')] = '2'
        self.assert_no_size_call()
        del self.d[('S', 'T', 'P', '-Z', '-D')]
        self.assert_size_call(2)

    def test_dictionary_update_no_shrink(self):
        self.t.set_min_undo_length(7)
        self.d[('S', 'T', 'P', '-Z', '-D')] = '1'
        del self.d[('S', 'T', 'P', '-Z', '-D')]
        self.assert_size_call(7)

    def test_translation_calls_restrict(self):
        self.t.translate(stroke('S'))
        self.assert_size_call(0)
예제 #5
0
 def setUp(self):
     self.t = Translator()
     self.s = type(self).FakeState()
     self.t._state = self.s
     self.d = StenoDictionary()
     self.dc = StenoDictionaryCollection()
     self.dc.set_dicts([self.d])
     self.t.set_dictionary(self.dc)
예제 #6
0
 def setUp(self):
     self.d = StenoDictionary()
     self.dc = StenoDictionaryCollection()
     self.dc.set_dicts([self.d])
     self.s = _State()
     self.o = self.CaptureOutput()
     self.tlor = Translator()
     self.tlor.set_dictionary(self.dc)
     self.tlor.add_listener(self.o)
     self.tlor.set_state(self.s)
예제 #7
0
    def test_dictionary_collection_longest_key(self):

        k1 = ('S',)
        k2 = ('S', 'T')
        k3 = ('S', 'T' , 'R')

        dc = StenoDictionaryCollection()
        self.assertEqual(dc.longest_key, 0)

        d1 = StenoDictionary()
        d1._path = 'd1'
        d1.save = lambda: None
        d1[k1] = 'a'

        dc.set_dicts([d1])
        self.assertEqual(dc.longest_key, 1)

        d1[k2] = 'a'
        self.assertEqual(dc.longest_key, 2)

        d2 = StenoDictionary()
        d2._path = 'd2'
        d2[k3] = 'c'

        dc.set_dicts([d1, d2])
        self.assertEqual(dc.longest_key, 3)

        del d1[k2]
        self.assertEqual(dc.longest_key, 3)

        dc.set_dicts([d1])
        self.assertEqual(dc.longest_key, 1)

        dc.set_dicts([])
        self.assertEqual(dc.longest_key, 0)
예제 #8
0
def test_dictionary_collection_writeable():
    d1 = StenoDictionary()
    d1[('S',)] = 'a'
    d1[('T',)] = 'b'
    d2 = StenoDictionary()
    d2[('S',)] = 'c'
    d2[('W',)] = 'd'
    d2.readonly = True
    dc = StenoDictionaryCollection([d2, d1])
    assert dc.first_writable() == d1
    dc.set(('S',), 'A')
    assert d1[('S',)] == 'A'
    assert d2[('S',)] == 'c'
 def test_dictionary_collection_writeable(self):
     d1 = StenoDictionary()
     d1[('S',)] = 'a'
     d1[('T',)] = 'b'
     d2 = StenoDictionary()
     d2[('S',)] = 'c'
     d2[('W',)] = 'd'
     d2.readonly = True
     dc = StenoDictionaryCollection([d2, d1])
     self.assertEqual(dc.first_writable(), d1)
     dc.set(('S',), 'A')
     self.assertEqual(d1[('S',)], 'A')
     self.assertEqual(d2[('S',)], 'c')
 def test_dictionary_collection_writeable(self):
     d1 = StenoDictionary()
     d1[('S', )] = 'a'
     d1[('T', )] = 'b'
     d2 = StenoDictionary()
     d2[('S', )] = 'c'
     d2[('W', )] = 'd'
     d2.readonly = True
     dc = StenoDictionaryCollection([d2, d1])
     self.assertEqual(dc.first_writable(), d1)
     dc.set(('S', ), 'A')
     self.assertEqual(d1[('S', )], 'A')
     self.assertEqual(d2[('S', )], 'c')
예제 #11
0
def test_dictionary_collection_writeable():
    d1 = StenoDictionary()
    d1[('S', )] = 'a'
    d1[('T', )] = 'b'
    d2 = StenoDictionary()
    d2[('S', )] = 'c'
    d2[('W', )] = 'd'
    d2.readonly = True
    dc = StenoDictionaryCollection([d2, d1])
    assert dc.first_writable() == d1
    dc.set(('S', ), 'A')
    assert d1[('S', )] == 'A'
    assert d2[('S', )] == 'c'
예제 #12
0
 def _set_dictionaries(self, dictionaries):
     def dictionaries_changed(l1, l2):
         if len(l1) != len(l2):
             return True
         for d1, d2 in zip(l1, l2):
             if d1 is not d2:
                 return True
         return False
     if not dictionaries_changed(dictionaries, self._dictionaries.dicts):
         # No change.
         return
     self._dictionaries = StenoDictionaryCollection(dictionaries)
     self._translator.set_dictionary(self._dictionaries)
     self._trigger_hook('dictionaries_loaded', self._dictionaries)
예제 #13
0
파일: translation.py 프로젝트: ArenM/plover
 def __init__(self):
     self._undo_length = 0
     self._dictionary = None
     self.set_dictionary(StenoDictionaryCollection())
     self._listeners = set()
     self._state = _State()
     self._to_undo = []
     self._to_do = 0
예제 #14
0
 def setup_method(self):
     self.stroke_limit = 8
     self.t = Translator(self.stroke_limit)
     self.s = type(self).FakeState()
     self.t._state = self.s
     self.d = StenoDictionary()
     self.dc = StenoDictionaryCollection([self.d])
     self.t.set_dictionary(self.dc)
예제 #15
0
def test_casereverse_lookup():
    dc = StenoDictionaryCollection()

    d1 = StenoDictionary()
    d1[('PWAOUFL', )] = 'beautiful'
    d1[('WAOUFL', )] = 'beAuTIFul'

    d2 = StenoDictionary()
    d2[('PW-FL', )] = 'BEAUTIFUL'

    d3 = StenoDictionary()
    d3[('WAOUFL', )] = 'not beautiful'

    dc.set_dicts([d1, d2, d3])

    assert dc.casereverse_lookup('beautiful') == {
        'beautiful', 'BEAUTIFUL', 'beAuTIFul'
    }
예제 #16
0
 def __init__(self, stroke_limit=KEY_STROKE_LIMIT):
     self._stroke_limit = stroke_limit
     self._undo_length = 0
     self._dictionary = None
     self.set_dictionary(StenoDictionaryCollection())
     self._listeners = set()
     self._state = _State()
     self._to_undo = []
     self._to_do = 0
예제 #17
0
    def test_changing_state(self):
        output = []
        def listener(undo, do, prev):
            prev = list(prev) if prev else None
            output.append((undo, do, prev))

        d = StenoDictionary()
        d[('S', 'P')] = 'hi'
        dc = StenoDictionaryCollection()
        dc.set_dicts([d])
        t = Translator()
        t.set_dictionary(dc)
        t.translate(stroke('T'))
        t.translate(stroke('S'))
        s = copy.deepcopy(t.get_state())
        
        t.add_listener(listener)
        
        expected = [([Translation([stroke('S')], None)], 
                     [Translation([stroke('S'), stroke('P')], 'hi')], 
                     [Translation([stroke('T')], None)])]
        t.translate(stroke('P'))
        self.assertEqual(output, expected)
        
        del output[:]
        t.set_state(s)
        t.translate(stroke('P'))
        self.assertEqual(output, expected)
        
        del output[:]
        t.clear_state()
        t.translate(stroke('P'))
        self.assertEqual(output, [([], [Translation([stroke('P')], None)], None)])
        
        del output[:]
        t.set_state(s)
        t.translate(stroke('P'))
        self.assertEqual(output, 
                         [([], 
                           [Translation([stroke('P')], None)], 
                           [Translation([stroke('S'), stroke('P')], 'hi')])])
예제 #18
0
파일: engine.py 프로젝트: ohAitch/plover
 def _set_dictionaries(self, dictionaries):
     def dictionaries_changed(l1, l2):
         if len(l1) != len(l2):
             return True
         for d1, d2 in zip(l1, l2):
             if d1 is not d2:
                 return True
         return False
     if not dictionaries_changed(dictionaries, self._dictionaries.dicts):
         # No change.
         return
     self._dictionaries = StenoDictionaryCollection(dictionaries)
     self._translator.set_dictionary(self._dictionaries)
     self._trigger_hook('dictionaries_loaded', self._dictionaries)
예제 #19
0
def model_test(monkeypatch, request):
    state = request.function.__doc__
    # Patch configuration directory.
    current_dir = Path('.').resolve()
    monkeypatch.setattr('plover.misc.CONFIG_DIR', str(current_dir))
    monkeypatch.setattr('plover.gui_qt.dictionaries_widget.CONFIG_DIR',
                        str(current_dir))
    # Disable i18n support.
    monkeypatch.setattr('plover.gui_qt.dictionaries_widget._', lambda s: s)
    # Fake config.
    config = mock.PropertyMock()
    config.return_value = {}
    # Dictionaries.
    dictionaries = StenoDictionaryCollection()
    # Fake engine.
    engine = mock.MagicMock(spec='''
                            __enter__ __exit__
                            config signal_connect
                            '''.split())
    engine.__enter__.return_value = engine
    type(engine).config = config
    signals = mock.MagicMock()
    config.return_value = {
        'dictionaries': config_dictionaries_from_state(state) if state else [],
        'classic_dictionaries_display_order': False,
    }
    # Setup model.
    model = DictionariesModel(engine, {name: name
                                       for name in ICON_TO_CHAR},
                              max_undo=5)
    for slot in '''
    dataChanged
    layoutAboutToBeChanged
    layoutChanged
    has_undo_changed
    '''.split():
        getattr(model, slot).connect(getattr(signals, slot))
    connections = dict(call.args for call in engine.signal_connect.mock_calls)
    assert connections.keys() == {'config_changed', 'dictionaries_loaded'}
    config.reset_mock()
    engine.reset_mock()
    # Test helper.
    test = ModelTest(config, dictionaries, engine, model, signals, connections,
                     state)
    if state and any(icon != 'loading'
                     for enabled, icon, path in parse_state(state)):
        test.load_dictionaries(state)
        test.reset_mocks()
    return test
예제 #20
0
 def setUp(self):
     self.d = StenoDictionary()
     self.dc = StenoDictionaryCollection()
     self.dc.set_dicts([self.d])
     self.s = _State()
     self.o = type(self).CaptureOutput()
예제 #21
0
 def test_dictionary_collection(self):
     dc = StenoDictionaryCollection()
     d1 = StenoDictionary()
     d1[('S',)] = 'a'
     d1[('T',)] = 'b'
     d2 = StenoDictionary()
     d2[('S',)] = 'c'
     d2[('W',)] = 'd'
     dc.set_dicts([d1, d2])
     self.assertEqual(dc.lookup(('S',)), 'c')
     self.assertEqual(dc.lookup(('W',)), 'd')
     self.assertEqual(dc.lookup(('T',)), 'b')
     f = lambda k, v: v == 'c'
     dc.add_filter(f)
     self.assertIsNone(dc.lookup(('S',)))
     self.assertEqual(dc.raw_lookup(('S',)), 'c')
     self.assertEqual(dc.lookup(('W',)), 'd')
     self.assertEqual(dc.lookup(('T',)), 'b')
     self.assertEqual(dc.reverse_lookup('c'), [('S',)])
     
     dc.remove_filter(f)
     self.assertEqual(dc.lookup(('S',)), 'c')
     self.assertEqual(dc.lookup(('W',)), 'd')
     self.assertEqual(dc.lookup(('T',)), 'b')
     
     self.assertEqual(dc.reverse_lookup('c'), [('S',)])
     
     dc.set(('S',), 'e')
     self.assertEqual(dc.lookup(('S',)), 'e')
     self.assertEqual(d2[('S',)], 'e')
예제 #22
0
def test_dictionary_collection():
    d1 = StenoDictionary()
    d1[('S',)] = 'a'
    d1[('T',)] = 'b'
    d1.path = 'd1'
    d2 = StenoDictionary()
    d2[('S',)] = 'c'
    d2[('W',)] = 'd'
    d2.path = 'd2'
    dc = StenoDictionaryCollection([d2, d1])
    assert dc.lookup(('S',)) == 'c'
    assert dc.lookup(('W',)) == 'd'
    assert dc.lookup(('T',)) == 'b'
    f = lambda k, v: v == 'c'
    dc.add_filter(f)
    assert dc.lookup(('S',)) is None
    assert dc.raw_lookup(('S',)) == 'c'
    assert dc.lookup(('W',)) == 'd'
    assert dc.lookup(('T',)) == 'b'
    assert dc.reverse_lookup('c') == [('S',)]

    dc.remove_filter(f)
    assert dc.lookup(('S',)) == 'c'
    assert dc.lookup(('W',)) == 'd'
    assert dc.lookup(('T',)) == 'b'

    assert dc.reverse_lookup('c') == [('S',)]

    dc.set(('S',), 'e')
    assert dc.lookup(('S',)) == 'e'
    assert d2[('S',)] == 'e'

    dc.set(('S',), 'f', path='d1')
    assert dc.lookup(('S',)) == 'e'
    assert d1[('S',)] == 'f'
    assert d2[('S',)] == 'e'

    # Iterating on a StenoDictionaryCollection is
    # the same as iterating on its dictionaries' paths.
    assert list(dc) == ['d2', 'd1']

    # Test get and [].
    assert dc.get('d1') == d1
    assert dc['d1'] == d1
    assert dc.get('invalid') is None
    with pytest.raises(KeyError):
        dc['invalid']
    def test_reverse_lookup(self):
        dc = StenoDictionaryCollection()

        d1 = StenoDictionary()
        d1[('PWAOUFL',)] = 'beautiful'
        d1[('WAOUFL',)] = 'beautiful'

        d2 = StenoDictionary()
        d2[('PW-FL',)] = 'beautiful'

        d3 = StenoDictionary()
        d3[('WAOUFL',)] = 'not beautiful'

        # Simple test.
        dc.set_dicts([d1])
        assertCountEqual(self,
                         dc.reverse_lookup('beautiful'),
                         [('PWAOUFL',), ('WAOUFL',)])

        # No duplicates.
        dc.set_dicts([d2, StenoDictionary(d2)])
        assertCountEqual(self,
                         dc.reverse_lookup('beautiful'),
                         [('PW-FL',)])

        # Don't stop at the first dictionary with matches.
        dc.set_dicts([d1, d2])
        assertCountEqual(self,
                         dc.reverse_lookup('beautiful'),
                         [('PWAOUFL',), ('WAOUFL',), ('PW-FL',)])

        # Ignore keys overriden by a higher precedence dictionary.
        dc.set_dicts([d1, d2, d3])
        assertCountEqual(self,
                         dc.reverse_lookup('beautiful'),
                         [('PWAOUFL',), ('PW-FL',)])
예제 #24
0
def test_dictionary_collection():
    d1 = StenoDictionary()
    d1[('S', )] = 'a'
    d1[('T', )] = 'b'
    d1.path = 'd1'
    d2 = StenoDictionary()
    d2[('S', )] = 'c'
    d2[('W', )] = 'd'
    d2.path = 'd2'
    dc = StenoDictionaryCollection([d2, d1])
    assert dc.lookup(('S', )) == 'c'
    assert dc.lookup(('W', )) == 'd'
    assert dc.lookup(('T', )) == 'b'
    f = lambda k, v: v == 'c'
    dc.add_filter(f)
    assert dc.lookup(('S', )) is None
    assert dc.raw_lookup(('S', )) == 'c'
    assert dc.lookup(('W', )) == 'd'
    assert dc.lookup(('T', )) == 'b'
    assert dc.reverse_lookup('c') == {('S', )}

    dc.remove_filter(f)
    assert dc.lookup(('S', )) == 'c'
    assert dc.lookup(('W', )) == 'd'
    assert dc.lookup(('T', )) == 'b'

    assert dc.reverse_lookup('c') == {('S', )}

    dc.set(('S', ), 'e')
    assert dc.lookup(('S', )) == 'e'
    assert d2[('S', )] == 'e'

    dc.set(('S', ), 'f', path='d1')
    assert dc.lookup(('S', )) == 'e'
    assert d1[('S', )] == 'f'
    assert d2[('S', )] == 'e'

    # Iterating on a StenoDictionaryCollection is
    # the same as iterating on its dictionaries' paths.
    assert list(dc) == ['d2', 'd1']

    # Test get and [].
    assert dc.get('d1') == d1
    assert dc['d1'] == d1
    assert dc.get('invalid') is None
    with pytest.raises(KeyError):
        dc['invalid']
예제 #25
0
def test_dictionary_enabled():
    dc = StenoDictionaryCollection()
    d1 = StenoDictionary()
    d1.path = 'd1'
    d1[('TEFT', )] = 'test1'
    d1[('TEFGT', )] = 'Testing'
    d2 = StenoDictionary()
    d2[('TEFT', )] = 'test2'
    d2[('TEFT', '-G')] = 'Testing'
    d2.path = 'd2'
    dc.set_dicts([d2, d1])
    assert dc.lookup(('TEFT', )) == 'test2'
    assert dc.raw_lookup(('TEFT', )) == 'test2'
    assert dc.casereverse_lookup('testing') == ['Testing']
    assert dc.reverse_lookup('Testing') == {('TEFT', '-G'), ('TEFGT', )}
    d2.enabled = False
    assert dc.lookup(('TEFT', )) == 'test1'
    assert dc.raw_lookup(('TEFT', )) == 'test1'
    assert dc.casereverse_lookup('testing') == ['Testing']
    assert dc.reverse_lookup('Testing') == {('TEFGT', )}
    d1.enabled = False
    assert dc.lookup(('TEST', )) is None
    assert dc.raw_lookup(('TEFT', )) is None
    assert dc.casereverse_lookup('testing') == []
    assert dc.reverse_lookup('Testing') == set()
    def test_dictionary_collection(self):
        d1 = StenoDictionary()
        d1[('S', )] = 'a'
        d1[('T', )] = 'b'
        d1.path = 'd1'
        d2 = StenoDictionary()
        d2[('S', )] = 'c'
        d2[('W', )] = 'd'
        d2.path = 'd2'
        dc = StenoDictionaryCollection([d2, d1])
        self.assertEqual(dc.lookup(('S', )), 'c')
        self.assertEqual(dc.lookup(('W', )), 'd')
        self.assertEqual(dc.lookup(('T', )), 'b')
        f = lambda k, v: v == 'c'
        dc.add_filter(f)
        self.assertIsNone(dc.lookup(('S', )))
        self.assertEqual(dc.raw_lookup(('S', )), 'c')
        self.assertEqual(dc.lookup(('W', )), 'd')
        self.assertEqual(dc.lookup(('T', )), 'b')
        self.assertEqual(dc.reverse_lookup('c'), [('S', )])

        dc.remove_filter(f)
        self.assertEqual(dc.lookup(('S', )), 'c')
        self.assertEqual(dc.lookup(('W', )), 'd')
        self.assertEqual(dc.lookup(('T', )), 'b')

        self.assertEqual(dc.reverse_lookup('c'), [('S', )])

        dc.set(('S', ), 'e')
        self.assertEqual(dc.lookup(('S', )), 'e')
        self.assertEqual(d2[('S', )], 'e')

        dc.set(('S', ), 'f', path='d1')
        self.assertEqual(dc.lookup(('S', )), 'e')
        self.assertEqual(d1[('S', )], 'f')
        self.assertEqual(d2[('S', )], 'e')

        # Iterating on a StenoDictionaryCollection is
        # the same as iterating on its dictionaries' paths.
        self.assertEqual(list(dc), ['d2', 'd1'])

        # Test get and [].
        self.assertEqual(dc.get('d1'), d1)
        self.assertEqual(dc['d1'], d1)
        self.assertEqual(dc.get('invalid'), None)
        with self.assertRaises(KeyError):
            dc['invalid']
예제 #27
0
def test_dictionary_enabled():
    dc = StenoDictionaryCollection()
    d1 = StenoDictionary()
    d1.path = 'd1'
    d1[('TEFT',)] = 'test1'
    d1[('TEFGT',)] = 'Testing'
    d2 = StenoDictionary()
    d2[('TEFT',)] = 'test2'
    d2[('TEFT', '-G')] = 'Testing'
    d2.path = 'd2'
    dc.set_dicts([d2, d1])
    assert dc.lookup(('TEFT',)) == 'test2'
    assert dc.raw_lookup(('TEFT',)) == 'test2'
    assert dc.casereverse_lookup('testing') == ['Testing']
    assert dc.reverse_lookup('Testing') == [('TEFT', '-G'), ('TEFGT',)]
    d2.enabled = False
    assert dc.lookup(('TEFT',)) == 'test1'
    assert dc.raw_lookup(('TEFT',)) == 'test1'
    assert dc.casereverse_lookup('testing') == ['Testing']
    assert dc.reverse_lookup('Testing') == [('TEFGT',)]
    d1.enabled = False
    assert dc.lookup(('TEST',)) is None
    assert dc.raw_lookup(('TEFT',)) is None
    assert dc.casereverse_lookup('testing') is None
    assert dc.reverse_lookup('Testing') == []
예제 #28
0
 def test_dictionary_enabled(self):
     dc = StenoDictionaryCollection()
     d1 = StenoDictionary()
     d1.path = 'd1'
     d1[('TEFT',)] = 'test1'
     d1[('TEFGT',)] = 'Testing'
     d2 = StenoDictionary()
     d2[('TEFT',)] = 'test2'
     d2[('TEFT','-G')] = 'Testing'
     d2.path = 'd2'
     dc.set_dicts([d2, d1])
     self.assertEqual(dc.lookup(('TEFT',)), 'test2')
     self.assertEqual(dc.raw_lookup(('TEFT',)), 'test2')
     self.assertEqual(dc.casereverse_lookup('testing'), ['Testing'])
     assertCountEqual(self, dc.reverse_lookup('Testing'), [('TEFGT',), ('TEFT', '-G')])
     d2.enabled = False
     self.assertEqual(dc.lookup(('TEFT',)), 'test1')
     self.assertEqual(dc.raw_lookup(('TEFT',)), 'test1')
     self.assertEqual(dc.casereverse_lookup('testing'), ['Testing'])
     assertCountEqual(self, dc.reverse_lookup('Testing'), [('TEFGT',)])
     d1.enabled = False
     self.assertEqual(dc.lookup(('TEST',)), None)
     self.assertEqual(dc.raw_lookup(('TEFT',)), None)
     self.assertEqual(dc.casereverse_lookup('testing'), None)
     assertCountEqual(self, dc.reverse_lookup('Testing'), [])
예제 #29
0
    def test_dictionary_collection(self):
        dc = StenoDictionaryCollection()
        d1 = StenoDictionary()
        d1[('S', )] = 'a'
        d1[('T', )] = 'b'
        d2 = StenoDictionary()
        d2[('S', )] = 'c'
        d2[('W', )] = 'd'
        dc.set_dicts([d1, d2])
        self.assertEqual(dc.lookup(('S', )), 'c')
        self.assertEqual(dc.lookup(('W', )), 'd')
        self.assertEqual(dc.lookup(('T', )), 'b')
        f = lambda k, v: v == 'c'
        dc.add_filter(f)
        self.assertIsNone(dc.lookup(('S', )))
        self.assertEqual(dc.raw_lookup(('S', )), 'c')
        self.assertEqual(dc.lookup(('W', )), 'd')
        self.assertEqual(dc.lookup(('T', )), 'b')
        self.assertEqual(dc.reverse_lookup('c'), [('S', )])

        dc.remove_filter(f)
        self.assertEqual(dc.lookup(('S', )), 'c')
        self.assertEqual(dc.lookup(('W', )), 'd')
        self.assertEqual(dc.lookup(('T', )), 'b')

        self.assertEqual(dc.reverse_lookup('c'), [('S', )])

        dc.set(('S', ), 'e')
        self.assertEqual(dc.lookup(('S', )), 'e')
        self.assertEqual(d2[('S', )], 'e')
예제 #30
0
 def setUp(self):
     self.d = StenoDictionary()
     self.dc = StenoDictionaryCollection()
     self.dc.set_dicts([self.d])
     self.s = _State()
     self.o = type(self).CaptureOutput()
예제 #31
0
def test_reverse_lookup():
    dc = StenoDictionaryCollection()

    d1 = StenoDictionary()
    d1[('PWAOUFL',)] = 'beautiful'
    d1[('WAOUFL',)] = 'beautiful'

    d2 = StenoDictionary()
    d2[('PW-FL',)] = 'beautiful'

    d3 = StenoDictionary()
    d3[('WAOUFL',)] = 'not beautiful'

    # Simple test.
    dc.set_dicts([d1])
    assert dc.reverse_lookup('beautiful') == [('PWAOUFL',), ('WAOUFL',)]

    # No duplicates.
    d2_copy = StenoDictionary()
    d2_copy.update(d2)
    dc.set_dicts([d2_copy, d2])
    assert dc.reverse_lookup('beautiful') == [('PW-FL',)]

    # Don't stop at the first dictionary with matches.
    dc.set_dicts([d2, d1])
    assert dc.reverse_lookup('beautiful') == [('PW-FL',), ('PWAOUFL',), ('WAOUFL',)]

    # Ignore keys overridden by a higher precedence dictionary.
    dc.set_dicts([d3, d2, d1])
    assert dc.reverse_lookup('beautiful') == [('PW-FL',), ('PWAOUFL',)]
예제 #32
0
    def test_dictionary_collection(self):
        dc = StenoDictionaryCollection()
        d1 = StenoDictionary()
        d1[('S',)] = 'a'
        d1[('T',)] = 'b'
        d1.path = 'd1'
        d2 = StenoDictionary()
        d2[('S',)] = 'c'
        d2[('W',)] = 'd'
        d2.path = 'd2'
        dc.set_dicts([d2, d1])
        self.assertEqual(dc.lookup(('S',)), 'c')
        self.assertEqual(dc.lookup(('W',)), 'd')
        self.assertEqual(dc.lookup(('T',)), 'b')
        f = lambda k, v: v == 'c'
        dc.add_filter(f)
        self.assertIsNone(dc.lookup(('S',)))
        self.assertEqual(dc.raw_lookup(('S',)), 'c')
        self.assertEqual(dc.lookup(('W',)), 'd')
        self.assertEqual(dc.lookup(('T',)), 'b')
        self.assertEqual(dc.reverse_lookup('c'), [('S',)])

        dc.remove_filter(f)
        self.assertEqual(dc.lookup(('S',)), 'c')
        self.assertEqual(dc.lookup(('W',)), 'd')
        self.assertEqual(dc.lookup(('T',)), 'b')

        self.assertEqual(dc.reverse_lookup('c'), [('S',)])

        dc.set(('S',), 'e')
        self.assertEqual(dc.lookup(('S',)), 'e')
        self.assertEqual(d2[('S',)], 'e')

        dc.set(('S',), 'f', path='d1')
        self.assertEqual(dc.lookup(('S',)), 'e')
        self.assertEqual(d1[('S',)], 'f')
        self.assertEqual(d2[('S',)], 'e')

        # Iterating on a StenoDictionaryCollection is
        # the same as iterating on its dictionaries' paths.
        self.assertEqual(list(dc), ['d2', 'd1'])

        # Test get and [].
        self.assertEqual(dc.get('d1'), d1)
        self.assertEqual(dc['d1'], d1)
        self.assertEqual(dc.get('invalid'), None)
        with self.assertRaises(KeyError):
            dc['invalid']
예제 #33
0
def test_translator():

    # It's not clear that this test is needed anymore. There are separate
    # tests for _translate_stroke and test_translate_calls_translate_stroke
    # makes sure that translate calls it properly. But since I already wrote
    # this test I'm going to keep it.

    class Output:
        def __init__(self):
            self._output = []

        def write(self, undo, do, prev):
            for t in undo:
                self._output.pop()
            for t in do:
                if t.english:
                    self._output.append(t.english)
                else:
                    self._output.append('/'.join(t.rtfcre))

        def get(self):
            return ' '.join(self._output)

        def clear(self):
            del self._output[:]

    d = StenoDictionary()
    out = Output()
    t = Translator()
    dc = StenoDictionaryCollection([d])
    t.set_dictionary(dc)
    t.add_listener(out.write)

    t.translate(stroke('S'))
    assert out.get() == 'S'
    t.translate(stroke('T'))
    assert out.get() == 'S T'
    t.translate(stroke('*'))
    assert out.get() == 'S'
    t.translate(stroke('*'))
    # Undo buffer ran out
    assert out.get() == 'S ' + BACK_STRING

    t.set_min_undo_length(3)
    out.clear()
    t.translate(stroke('S'))
    assert out.get() == 'S'
    t.translate(stroke('T'))
    assert out.get() == 'S T'
    t.translate(stroke('*'))
    assert out.get() == 'S'
    t.translate(stroke('*'))
    assert out.get() == ''

    out.clear()
    d[('S', )] = 't1'
    d[('T', )] = 't2'
    d[('S', 'T')] = 't3'

    t.translate(stroke('S'))
    assert out.get() == 't1'
    t.translate(stroke('T'))
    assert out.get() == 't3'
    t.translate(stroke('T'))
    assert out.get() == 't3 t2'
    t.translate(stroke('S'))
    assert out.get() == 't3 t2 t1'
    t.translate(stroke('*'))
    assert out.get() == 't3 t2'
    t.translate(stroke('*'))
    assert out.get() == 't3'
    t.translate(stroke('*'))
    assert out.get() == 't1'
    t.translate(stroke('*'))
    assert out.get() == ''

    t.translate(stroke('S'))
    assert out.get() == 't1'
    t.translate(stroke('T'))
    assert out.get() == 't3'
    t.translate(stroke('T'))
    assert out.get() == 't3 t2'

    d[('S', 'T', 'T')] = 't4'
    d[('S', 'T', 'T', 'S')] = 't5'

    t.translate(stroke('S'))
    assert out.get() == 't5'
    t.translate(stroke('*'))
    assert out.get() == 't3 t2'
    t.translate(stroke('*'))
    assert out.get() == 't3'
    t.translate(stroke('T'))
    assert out.get() == 't4'
    t.translate(stroke('S'))
    assert out.get() == 't5'
    t.translate(stroke('S'))
    assert out.get() == 't5 t1'
    t.translate(stroke('*'))
    assert out.get() == 't5'
    t.translate(stroke('*'))
    assert out.get() == 't4'
    t.translate(stroke('*'))
    assert out.get() == 't3'
    t.translate(stroke('*'))
    assert out.get() == 't1'
    t.translate(stroke('*'))
    assert out.get() == ''

    d.clear()

    s = stroke('S')
    t.translate(s)
    t.translate(s)
    t.translate(s)
    t.translate(s)
    s = stroke('*')
    t.translate(s)
    t.translate(s)
    t.translate(s)
    t.translate(s)
    # Not enough undo to clear output.
    assert out.get() == 'S ' + BACK_STRING

    out.clear()
    t.remove_listener(out.write)
    t.translate(stroke('S'))
    assert out.get() == ''
예제 #34
0
def test_search():
    dc = StenoDictionaryCollection()

    # Similarity is based on string equality after removing case and stripping special characters from the ends.
    d1 = StenoDictionary()
    d1[('WAOUFL', )] = 'beautiful'
    d1[('PWAOUFL', )] = 'Beautiful'
    d1[('PWAOUT', '-FL')] = '{^BEAUTIFUL}  '
    d1[('ULG', )] = 'ugly'
    dc.set_dicts([d1])
    assert dc.find_similar('beautiful') == [('Beautiful', {('PWAOUFL', )}),
                                            ('beautiful', {('WAOUFL', )}),
                                            ('{^BEAUTIFUL}  ', {('PWAOUT',
                                                                 '-FL')})]

    assert dc.find_similar('{#BEAUtiful}{^}') == [
        ('Beautiful', {('PWAOUFL', )}), ('beautiful', {('WAOUFL', )}),
        ('{^BEAUTIFUL}  ', {('PWAOUT', '-FL')})
    ]

    # Translations found in multiple dicts should combine non-overlapping keys in the results.
    d2 = StenoDictionary()
    del d1[('PWAOUT', '-FL')]
    d2[('PW-FL', )] = 'beautiful'
    dc.set_dicts([d1, d2])
    assert dc.find_similar('beautiful') == [('Beautiful', {('PWAOUFL', )}),
                                            ('beautiful', {('WAOUFL', ),
                                                           ('PW-FL', )})]

    # If all possible keys for a translation are overridden, that translation should not be returned.
    d3 = StenoDictionary()
    d3[('PW-FL', )] = 'not beautiful'
    d3[('WAOUFL', )] = 'not beautiful'
    dc.set_dicts([d3, d1, d2])
    assert dc.find_similar('beautiful') == [('Beautiful', {('PWAOUFL', )})]

    # For partial word search, similar words will be returned first, but if the count is greater than that,
    # the next words in sorted order which are supersets are returned. Also stops at the end of the dictionary.
    dc.set_dicts([d1])
    d1[('PWAOU', )] = 'beau'
    d1[('PWAOUFL', 'HREU')] = 'beautifully'
    d1[('UG', 'HREU', '-PBS')] = 'ugliness'
    assert dc.find_partial('beau',
                           count=4) == [('beau', {('PWAOU', )}),
                                        ('Beautiful', {('PWAOUFL', )}),
                                        ('beautiful', {('WAOUFL', )}),
                                        ('beautifully', {('PWAOUFL', 'HREU')})]
    assert dc.find_partial('UGLY', count=2) == [('ugly', {('ULG', )})]

    # Even if a word isn't present, the search will return words going forward
    # from the index where it would be found if it was there.
    assert dc.find_partial('beaut',
                           count=3) == [('Beautiful', {('PWAOUFL', )}),
                                        ('beautiful', {('WAOUFL', )}),
                                        ('beautifully', {('PWAOUFL', 'HREU')})]

    # Regex search is straightforward; return up to count entries in order that match the given regular expression.
    # If no regex metacharacters are present, should just be a case-sensitive starts-with search.
    assert dc.find_regex('beau',
                         count=4) == [('beau', {('PWAOU', )}),
                                      ('beautiful', {('WAOUFL', )}),
                                      ('beautifully', {('PWAOUFL', 'HREU')})]
    assert dc.find_regex('beautiful.?.?',
                         count=2) == [('beautiful', {('WAOUFL', )}),
                                      ('beautifully', {('PWAOUFL', 'HREU')})]
    assert dc.find_regex(' beautiful', count=3) == []
    assert dc.find_regex('(b|u).{3}$', count=2) == [('beau', {('PWAOU', )}),
                                                    ('ugly', {('ULG', )})]
    assert dc.find_regex('.*ly',
                         count=5) == [('beautifully', {('PWAOUFL', 'HREU')}),
                                      ('ugly', {('ULG', )})]

    # Regex errors won't raise if the algorithm short circuits a pattern with no possible matches.
    assert dc.find_regex('an open group that doesn\'t raise(', count=5) == []
    with pytest.raises(re.error):
        print(dc.find_regex('beautiful...an open group(', count=1))
    def test_reverse_lookup(self):
        dc = StenoDictionaryCollection()

        d1 = StenoDictionary()
        d1[('PWAOUFL', )] = 'beautiful'
        d1[('WAOUFL', )] = 'beautiful'

        d2 = StenoDictionary()
        d2[('PW-FL', )] = 'beautiful'

        d3 = StenoDictionary()
        d3[('WAOUFL', )] = 'not beautiful'

        # Simple test.
        dc.set_dicts([d1])
        self.assertCountEqual(dc.reverse_lookup('beautiful'), [('PWAOUFL', ),
                                                               ('WAOUFL', )])

        # No duplicates.
        d2_copy = StenoDictionary()
        d2_copy.update(d2)
        dc.set_dicts([d2_copy, d2])
        self.assertCountEqual(dc.reverse_lookup('beautiful'), [('PW-FL', )])

        # Don't stop at the first dictionary with matches.
        dc.set_dicts([d2, d1])
        self.assertCountEqual(dc.reverse_lookup('beautiful'), [('PWAOUFL', ),
                                                               ('WAOUFL', ),
                                                               ('PW-FL', )])

        # Ignore keys overridden by a higher precedence dictionary.
        dc.set_dicts([d3, d2, d1])
        self.assertCountEqual(dc.reverse_lookup('beautiful'), [('PWAOUFL', ),
                                                               ('PW-FL', )])
 def test_dictionary_enabled(self):
     dc = StenoDictionaryCollection()
     d1 = StenoDictionary()
     d1.path = 'd1'
     d1[('TEFT', )] = 'test1'
     d1[('TEFGT', )] = 'Testing'
     d2 = StenoDictionary()
     d2[('TEFT', )] = 'test2'
     d2[('TEFT', '-G')] = 'Testing'
     d2.path = 'd2'
     dc.set_dicts([d2, d1])
     self.assertEqual(dc.lookup(('TEFT', )), 'test2')
     self.assertEqual(dc.raw_lookup(('TEFT', )), 'test2')
     self.assertEqual(dc.casereverse_lookup('testing'), ['Testing'])
     self.assertCountEqual(dc.reverse_lookup('Testing'), [('TEFGT', ),
                                                          ('TEFT', '-G')])
     d2.enabled = False
     self.assertEqual(dc.lookup(('TEFT', )), 'test1')
     self.assertEqual(dc.raw_lookup(('TEFT', )), 'test1')
     self.assertEqual(dc.casereverse_lookup('testing'), ['Testing'])
     self.assertCountEqual(dc.reverse_lookup('Testing'), [('TEFGT', )])
     d1.enabled = False
     self.assertEqual(dc.lookup(('TEST', )), None)
     self.assertEqual(dc.raw_lookup(('TEFT', )), None)
     self.assertEqual(dc.casereverse_lookup('testing'), None)
     self.assertCountEqual(dc.reverse_lookup('Testing'), [])
예제 #37
0
    def test_translator(self):

        # It's not clear that this test is needed anymore. There are separate 
        # tests for _translate_stroke and test_translate_calls_translate_stroke 
        # makes sure that translate calls it properly. But since I already wrote
        # this test I'm going to keep it.

        class Output(object):
            def __init__(self):
                self._output = []
                
            def write(self, undo, do, prev):
                for t in undo:
                    self._output.pop()
                for t in do:
                    if t.english:
                        self._output.append(t.english)
                    else:
                        self._output.append('/'.join(t.rtfcre))
                        
            def get(self):
                return ' '.join(self._output)
                
            def clear(self):
                del self._output[:]
                
        d = StenoDictionary()        
        out = Output()        
        t = Translator()
        dc = StenoDictionaryCollection()
        dc.set_dicts([d])
        t.set_dictionary(dc)
        t.add_listener(out.write)
        
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 'S')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 'S T')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 'S')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 'S ' + _back_string())  # Undo buffer ran out.
        
        t.set_min_undo_length(3)
        out.clear()
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 'S')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 'S T')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 'S')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), '' )

        out.clear()
        d[('S',)] = 't1'
        d[('T',)] = 't2'
        d[('S', 'T')] = 't3'
        
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 't1')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 't3')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 't3 t2')
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 't3 t2 t1')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't3 t2')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't3')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't1')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), '')
        
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 't1')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 't3')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 't3 t2')

        d[('S', 'T', 'T')] = 't4'
        d[('S', 'T', 'T', 'S')] = 't5'
        
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 't5')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't3 t2')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't3')
        t.translate(stroke('T'))
        self.assertEqual(out.get(), 't4')
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 't5')
        t.translate(stroke('S'))
        self.assertEqual(out.get(), 't5 t1')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't5')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't4')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't3')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), 't1')
        t.translate(stroke('*'))
        self.assertEqual(out.get(), '')
        
        d.clear()

        s = stroke('S')
        t.translate(s)
        t.translate(s)
        t.translate(s)
        t.translate(s)
        s = stroke('*')
        t.translate(s)
        t.translate(s)
        t.translate(s)
        t.translate(s)
        self.assertEqual(out.get(), 'S ' + _back_string())  # Not enough undo to clear output.
        
        out.clear()
        t.remove_listener(out.write)
        t.translate(stroke('S'))
        self.assertEqual(out.get(), '')
예제 #38
0
class StenoEngine:

    HOOKS = '''
    stroked
    translated
    machine_state_changed
    output_changed
    config_changed
    dictionaries_loaded
    send_string
    send_backspaces
    send_key_combination
    add_translation
    focus
    configure
    lookup
    quit
    '''.split()

    def __init__(self, config, keyboard_emulation):
        self._config = config
        self._is_running = False
        self._queue = Queue()
        self._lock = threading.RLock()
        self._machine = None
        self._machine_state = None
        self._machine_params = MachineParams(None, None, None)
        self._formatter = Formatter()
        self._formatter.set_output(self)
        self._formatter.add_listener(self._on_translated)
        self._translator = Translator()
        self._translator.add_listener(log.translation)
        self._translator.add_listener(self._formatter.format)
        self._dictionaries = self._translator.get_dictionary()
        self._dictionaries_manager = DictionaryLoadingManager()
        self._running_state = self._translator.get_state()
        self._keyboard_emulation = keyboard_emulation
        self._hooks = {hook: [] for hook in self.HOOKS}
        self._running_extensions = {}

    def __enter__(self):
        self._lock.__enter__()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self._lock.__exit__(exc_type, exc_value, traceback)

    def _in_engine_thread(self):
        raise NotImplementedError()

    def _same_thread_hook(self, func, *args, **kwargs):
        if self._in_engine_thread():
            func(*args, **kwargs)
        else:
            self._queue.put((func, args, kwargs))

    def run(self):
        while True:
            func, args, kwargs = self._queue.get()
            try:
                with self._lock:
                    if func(*args, **kwargs):
                        break
            except Exception:
                log.error('engine %s failed', func.__name__[1:], exc_info=True)

    def _stop(self):
        self._stop_extensions(self._running_extensions.keys())
        if self._machine is not None:
            self._machine.stop_capture()
            self._machine = None

    def _start(self):
        self._set_output(self._config['auto_start'])
        self._update(full=True)

    def _set_dictionaries(self, dictionaries):
        def dictionaries_changed(l1, l2):
            if len(l1) != len(l2):
                return True
            for d1, d2 in zip(l1, l2):
                if d1 is not d2:
                    return True
            return False

        if not dictionaries_changed(dictionaries, self._dictionaries.dicts):
            # No change.
            return
        self._dictionaries = StenoDictionaryCollection(dictionaries)
        self._translator.set_dictionary(self._dictionaries)
        self._trigger_hook('dictionaries_loaded', self._dictionaries)

    def _update(self, config_update=None, full=False, reset_machine=False):
        original_config = self._config.as_dict()
        # Update configuration.
        if config_update is not None:
            self._config.update(**config_update)
            config = self._config.as_dict()
        else:
            config = original_config
        # Create configuration update.
        if full:
            config_update = config
        else:
            config_update = {
                option: value
                for option, value in config.items()
                if value != original_config[option]
            }
        # Update logging.
        log.set_stroke_filename(config['log_file_name'])
        log.enable_stroke_logging(config['enable_stroke_logging'])
        log.enable_translation_logging(config['enable_translation_logging'])
        # Update output.
        self._formatter.set_space_placement(config['space_placement'])
        self._formatter.start_attached = config['start_attached']
        self._formatter.start_capitalized = config['start_capitalized']
        self._translator.set_min_undo_length(config['undo_levels'])
        # Update system.
        system_name = config['system_name']
        if system.NAME != system_name:
            log.info('loading system: %s', system_name)
            system.setup(system_name)
        # Update machine.
        update_keymap = False
        start_machine = False
        machine_params = MachineParams(config['machine_type'],
                                       config['machine_specific_options'],
                                       config['system_keymap'])
        # Do not reset if only the keymap changed.
        if self._machine_params is None or \
           self._machine_params.type != machine_params.type or \
           self._machine_params.options != machine_params.options:
            reset_machine = True
        if reset_machine:
            if self._machine is not None:
                self._machine.stop_capture()
                self._machine = None
            machine_type = config['machine_type']
            machine_options = config['machine_specific_options']
            machine_class = registry.get_plugin('machine', machine_type).obj
            log.info('setting machine: %s', machine_type)
            self._machine = machine_class(machine_options)
            self._machine.set_suppression(self._is_running)
            self._machine.add_state_callback(self._machine_state_callback)
            self._machine.add_stroke_callback(self._machine_stroke_callback)
            self._machine_params = machine_params
            update_keymap = True
            start_machine = True
        elif self._machine is not None:
            update_keymap = 'system_keymap' in config_update
        if update_keymap:
            machine_keymap = config['system_keymap']
            if machine_keymap is not None:
                self._machine.set_keymap(machine_keymap)
        if start_machine:
            self._machine.start_capture()
        # Update running extensions.
        enabled_extensions = config['enabled_extensions']
        running_extensions = set(self._running_extensions)
        self._stop_extensions(running_extensions - enabled_extensions)
        self._start_extensions(enabled_extensions - running_extensions)
        # Trigger `config_changed` hook.
        if config_update:
            self._trigger_hook('config_changed', config_update)
        # Update dictionaries.
        config_dictionaries = OrderedDict(
            (d.path, d) for d in config['dictionaries'])
        copy_default_dictionaries(config_dictionaries.keys())
        # Start by unloading outdated dictionaries.
        self._dictionaries_manager.unload_outdated()
        self._set_dictionaries([
            d for d in self._dictionaries.dicts
            if d.path in config_dictionaries and \
               d.path in self._dictionaries_manager
        ])
        # And then (re)load all dictionaries.
        dictionaries = []
        for result in self._dictionaries_manager.load(
                config_dictionaries.keys()):
            if isinstance(result, DictionaryLoaderException):
                d = ErroredDictionary(result.path, result.exception)
                # Only show an error if it's new.
                if d != self._dictionaries.get(result.path):
                    log.error('loading dictionary `%s` failed: %s',
                              shorten_path(result.path), str(result.exception))
            else:
                d = result
            d.enabled = config_dictionaries[d.path].enabled
            dictionaries.append(d)
        self._set_dictionaries(dictionaries)

    def _start_extensions(self, extension_list):
        for extension_name in extension_list:
            log.info('starting `%s` extension', extension_name)
            try:
                extension = registry.get_plugin('extension',
                                                extension_name).obj(self)
                extension.start()
            except Exception:
                log.error('initializing extension `%s` failed',
                          extension_name,
                          exc_info=True)
            else:
                self._running_extensions[extension_name] = extension

    def _stop_extensions(self, extension_list):
        for extension_name in list(extension_list):
            log.info('stopping `%s` extension', extension_name)
            extension = self._running_extensions.pop(extension_name)
            extension.stop()
            del extension

    def _quit(self, code):
        self._stop()
        self.code = code
        self._trigger_hook('quit')
        return True

    def _toggle_output(self):
        self._set_output(not self._is_running)

    def _set_output(self, enabled):
        if enabled == self._is_running:
            return
        self._is_running = enabled
        if enabled:
            self._translator.set_state(self._running_state)
        else:
            self._translator.clear_state()
        if self._machine is not None:
            self._machine.set_suppression(enabled)
        self._trigger_hook('output_changed', enabled)

    def _machine_state_callback(self, machine_state):
        self._same_thread_hook(self._on_machine_state_changed, machine_state)

    def _machine_stroke_callback(self, steno_keys):
        self._same_thread_hook(self._on_stroked, steno_keys)

    @with_lock
    def _on_machine_state_changed(self, machine_state):
        assert machine_state is not None
        self._machine_state = machine_state
        machine_type = self._config['machine_type']
        self._trigger_hook('machine_state_changed', machine_type,
                           machine_state)

    def _consume_engine_command(self, command):
        # The first commands can be used whether plover has output enabled or not.
        if command == 'RESUME':
            self._set_output(True)
            return True
        elif command == 'TOGGLE':
            self._toggle_output()
            return True
        elif command == 'QUIT':
            self.quit()
            return True
        if not self._is_running:
            return False
        # These commands can only be run when plover has output enabled.
        if command == 'SUSPEND':
            self._set_output(False)
        elif command == 'CONFIGURE':
            self._trigger_hook('configure')
        elif command == 'FOCUS':
            self._trigger_hook('focus')
        elif command == 'ADD_TRANSLATION':
            self._trigger_hook('add_translation')
        elif command == 'LOOKUP':
            self._trigger_hook('lookup')
        else:
            command_args = command.split(':', 1)
            command_fn = registry.get_plugin('command', command_args[0]).obj
            command_fn(self, command_args[1] if len(command_args) == 2 else '')
        return False

    def _on_stroked(self, steno_keys):
        stroke = Stroke(steno_keys)
        log.stroke(stroke)
        self._translator.translate(stroke)
        self._trigger_hook('stroked', stroke)

    def _on_translated(self, old, new):
        if not self._is_running:
            return
        self._trigger_hook('translated', old, new)

    def send_backspaces(self, b):
        if not self._is_running:
            return
        self._keyboard_emulation.send_backspaces(b)
        self._trigger_hook('send_backspaces', b)

    def send_string(self, s):
        if not self._is_running:
            return
        self._keyboard_emulation.send_string(s)
        self._trigger_hook('send_string', s)

    def send_key_combination(self, c):
        if not self._is_running:
            return
        self._keyboard_emulation.send_key_combination(c)
        self._trigger_hook('send_key_combination', c)

    def send_engine_command(self, command):
        suppress = not self._is_running
        suppress &= self._consume_engine_command(command)
        if suppress:
            self._machine.suppress_last_stroke(
                self._keyboard_emulation.send_backspaces)

    def toggle_output(self):
        self._same_thread_hook(self._toggle_output)

    def set_output(self, enabled):
        self._same_thread_hook(self._set_output, enabled)

    @property
    @with_lock
    def machine_state(self):
        return self._machine_state

    @property
    @with_lock
    def output(self):
        return self._is_running

    @output.setter
    def output(self, enabled):
        self._same_thread_hook(self._set_output, enabled)

    @property
    @with_lock
    def config(self):
        return self._config.as_dict()

    @config.setter
    def config(self, update):
        self._same_thread_hook(self._update, config_update=update)

    @with_lock
    def __getitem__(self, setting):
        return self._config[setting]

    def __setitem__(self, setting, value):
        self.config = {setting: value}

    def reset_machine(self):
        self._same_thread_hook(self._update, reset_machine=True)

    def load_config(self):
        try:
            with open(self._config.target_file, 'rb') as f:
                self._config.load(f)
        except Exception:
            log.error('loading configuration failed, resetting to default',
                      exc_info=True)
            self._config.clear()
            return False
        return True

    def start(self):
        self._same_thread_hook(self._start)

    def quit(self, code=0):
        # We need to go through the queue, even when already called
        # from the engine thread so _quit's return code does break
        # the thread out of its main loop.
        self._queue.put((self._quit, (code, ), {}))

    def restart(self):
        self.quit(-1)

    def join(self):
        return self.code

    @with_lock
    def lookup(self, translation):
        return self._dictionaries.lookup('/'.join(translation))

    @with_lock
    def raw_lookup(self, translation):
        return self._dictionaries.raw_lookup('/'.join(translation))

    @with_lock
    def reverse_lookup(self, translation):
        matches = self._dictionaries.reverse_lookup(translation)
        return [] if matches is None else matches

    @with_lock
    def casereverse_lookup(self, translation):
        matches = self._dictionaries.casereverse_lookup(translation)
        return set() if matches is None else matches

    @with_lock
    def add_dictionary_filter(self, dictionary_filter):
        self._dictionaries.add_filter(dictionary_filter)

    @with_lock
    def remove_dictionary_filter(self, dictionary_filter):
        self._dictionaries.remove_filter(dictionary_filter)

    @with_lock
    def get_suggestions(self, translation, **kwargs):
        return Suggestions(self._dictionaries).find(translation, **kwargs)

    @property
    @with_lock
    def translator_state(self):
        return self._translator.get_state()

    @translator_state.setter
    @with_lock
    def translator_state(self, state):
        self._translator.set_state(state)

    @with_lock
    def clear_translator_state(self, undo=False):
        if undo:
            state = self._translator.get_state()
            self._formatter.format(state.translations, (), None)
        self._translator.clear_state()

    @property
    @with_lock
    def starting_stroke_state(self):
        return StartingStrokeState(self._formatter.start_attached,
                                   self._formatter.start_capitalized)

    @starting_stroke_state.setter
    @with_lock
    def starting_stroke_state(self, state):
        self._formatter.start_attached = state.attach
        self._formatter.start_capitalized = state.capitalize

    @with_lock
    def add_translation(self, strokes, translation, dictionary_path=None):
        if dictionary_path is None:
            dictionary_path = self._dictionaries.first_writable().path
        self._dictionaries.set('/'.join(strokes),
                               translation,
                               path=dictionary_path)
        self._dictionaries.save(path_list=(dictionary_path, ))

    @property
    @with_lock
    def dictionaries(self):
        return self._dictionaries

    # Hooks.

    def _trigger_hook(self, hook, *args, **kwargs):
        for callback in self._hooks[hook]:
            try:
                callback(*args, **kwargs)
            except Exception:
                log.error('hook %r callback %r failed',
                          hook,
                          callback,
                          exc_info=True)

    @with_lock
    def hook_connect(self, hook, callback):
        self._hooks[hook].append(callback)

    @with_lock
    def hook_disconnect(self, hook, callback):
        self._hooks[hook].remove(callback)
예제 #39
0
class TranslateStrokeTestCase(unittest.TestCase):

    class CaptureOutput(object):
        output = namedtuple('output', 'undo do prev')
        
        def __init__(self):
            self.output = []

        def __call__(self, undo, new, prev):
            prev = list(prev) if prev else None
            self.output = type(self).output(undo, new, prev)

    def t(self, strokes):
        """A quick way to make a translation."""
        strokes = [stroke(x) for x in strokes.split('/')]
        key = tuple(s.rtfcre for s in strokes)
        translation = self.dc.lookup(key)
        return Translation(strokes, translation)

    def lt(self, translations):
        """A quick way to make a list of translations."""
        return [self.t(x) for x in translations.split()]

    def define(self, key, value):
        key = normalize_steno(key)
        self.d[key] = value

    def translate(self, stroke):
        self.tlor.translate(stroke)

    def assertTranslations(self, expected):
        self.assertEqual(self.s.translations, expected)

    def assertOutput(self, undo, do, prev):
        self.assertEqual(self.o.output, (undo, do, prev))

    def setUp(self):
        self.d = StenoDictionary()
        self.dc = StenoDictionaryCollection()
        self.dc.set_dicts([self.d])
        self.s = _State()
        self.o = self.CaptureOutput()
        self.tlor = Translator()
        self.tlor.set_dictionary(self.dc)
        self.tlor.add_listener(self.o)
        self.tlor.set_state(self.s)

    def test_first_stroke(self):
        self.translate(stroke('-B'))
        self.assertTranslations(self.lt('-B'))
        self.assertOutput([], self.lt('-B'), None)

    def test_second_stroke(self):
        self.define('S/P', 'spiders')
        self.s.translations = self.lt('S')
        self.translate(stroke('-T'))
        self.assertTranslations(self.lt('S -T'))
        self.assertOutput([], self.lt('-T'), self.lt('S'))

    def test_second_stroke_tail(self):
        self.s.tail = self.t('T/A/I/L')
        self.translate(stroke('-E'))
        self.assertTranslations(self.lt('E'))
        self.assertOutput([], self.lt('E'), self.lt('T/A/I/L'))

    def test_with_translation_1(self):
        self.define('S', 'is')
        self.define('-T', 'that')
        self.s.translations = self.lt('S')
        self.tlor.set_min_undo_length(2)
        self.translate(stroke('-T'))
        self.assertTranslations(self.lt('S -T'))
        self.assertOutput([], self.lt('-T'), self.lt('S'))
        self.assertEqual(self.o.output.do[0].english, 'that')

    def test_with_translation_2(self):
        self.define('S', 'is')
        self.define('-T', 'that')
        self.s.translations = self.lt('S')
        self.tlor.set_min_undo_length(1)
        self.translate(stroke('-T'))
        self.assertTranslations(self.lt('-T'))
        self.assertOutput([], self.lt('-T'), self.lt('S'))
        self.assertEqual(self.o.output.do[0].english, 'that')

    def test_finish_two_translation(self):
        self.define('S/T', 'hello')
        self.s.translations = self.lt('S')
        self.translate(stroke('T'))
        self.assertTranslations(self.lt('S/T'))
        self.assertOutput(self.lt('S'), self.lt('S/T'), None)
        self.assertEqual(self.o.output.do[0].english, 'hello')
        self.assertEqual(self.o.output.do[0].replaced, self.lt('S'))

    def test_finish_three_translation(self):
        self.define('S/T/-B', 'bye')
        self.s.translations = self.lt('S T')
        self.translate(stroke('-B'))
        self.assertTranslations(self.lt('S/T/-B'))
        self.assertOutput(self.lt('S T'), self.lt('S/T/-B'), None)
        self.assertEqual(self.o.output.do[0].english, 'bye')
        self.assertEqual(self.o.output.do[0].replaced, self.lt('S T'))

    def test_replace_translation(self):
        self.define('S/T/-B', 'longer')
        self.s.translations = self.lt('S/T')
        self.translate(stroke('-B'))
        self.assertTranslations(self.lt('S/T/-B'))
        self.assertOutput(self.lt('S/T'), self.lt('S/T/-B'), None)
        self.assertEqual(self.o.output.do[0].english, 'longer')
        self.assertEqual(self.o.output.do[0].replaced, self.lt('S/T'))

    def test_undo(self):
        self.s.translations = self.lt('POP')
        self.translate(stroke('*'))
        self.assertTranslations([])
        self.assertOutput(self.lt('POP'), [], None)

    def test_empty_undo(self):
        self.translate(stroke('*'))
        self.assertTranslations([])
        self.assertOutput([], [Translation([Stroke('*')], _back_string())], None)

    def test_undo_translation(self):
        self.define('P/P', 'pop')
        self.translate(stroke('P'))
        self.translate(stroke('P'))
        self.translate(stroke('*'))
        self.assertTranslations(self.lt('P'))
        self.assertOutput(self.lt('P/P'), self.lt('P'), None)

    def test_undo_longer_translation(self):
        self.define('P/P/-D', 'popped')
        self.translate(stroke('P'))
        self.translate(stroke('P'))
        self.translate(stroke('-D'))
        self.translate(stroke('*'))
        self.assertTranslations(self.lt('P P'))
        self.assertOutput(self.lt('P/P/-D'), self.lt('P P'), None)

    def test_undo_tail(self):
        self.s.tail = self.t('T/A/I/L')
        self.translate(stroke('*'))
        self.assertTranslations([])
        self.assertOutput([], [Translation([Stroke('*')], _back_string())], self.lt('T/A/I/L'))
        
    def test_suffix_folding(self):
        self.define('K-L', 'look')
        self.define('-G', '{^ing}')
        lt = self.lt('K-LG')
        lt[0].english = 'look {^ing}'
        self.translate(stroke('K-LG'))
        self.assertTranslations(lt)

    def test_suffix_folding_multi_stroke(self):
        self.define('E/HR', 'he will')
        self.define('-S', '{^s}')
        self.translate(stroke('E'))
        self.translate(stroke('HR-S'))
        output = ' '.join(t.english for t in self.s.translations)
        self.assertEqual(output, 'he will {^s}')

    def test_suffix_folding_doesnt_interfere(self):
        self.define('E/HR', 'he will')
        self.define('-S', '{^s}')
        self.define('E', 'he')
        self.define('HR-S', 'also')
        self.translate(stroke('E'))
        self.translate(stroke('HR-S'))
        output = ' '.join(t.english for t in self.s.translations)
        self.assertEqual(output, 'he also')

    def test_suffix_folding_no_suffix(self):
        self.define('K-L', 'look')
        lt = self.lt('K-LG')
        self.assertEqual(lt[0].english, None)
        self.translate(stroke('K-LG'))
        self.assertTranslations(lt)
        
    def test_suffix_folding_no_main(self):
        self.define('-G', '{^ing}')
        lt = self.lt('K-LG')
        self.assertEqual(lt[0].english, None)
        self.translate(stroke('K-LG'))
        self.assertTranslations(lt)

    def test_retrospective_insert_space(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('PER', 'perfect')
        self.define('SWAEUGS', 'situation')
        self.define('PER/SWAEUGS', 'persuasion')
        self.define('SP*', '{*?}')
        self.translate(stroke('PER'))
        self.translate(stroke('SWAEUGS'))
        self.translate(stroke('SP*'))
        lt = self.lt('PER')
        undo = self.lt('PER/SWAEUGS')
        undo[0].replaced = lt
        do = self.lt('SP*')
        do[0].english = 'perfect situation'
        do[0].is_retrospective_command = True
        do[0].replaced = undo
        self.assertTranslations(do)
        self.assertOutput(undo, do, None)

    def test_retrospective_insert_space_undefined(self):
        # Should work when beginning or ending strokes aren't defined
        self.define('T/E/S/T', 'a longer key')
        self.define('STWR/STWR', 'test')
        self.define('SP*', '{*?}')
        self.translate(stroke('STWR'))
        self.translate(stroke('STWR'))
        self.translate(stroke('SP*'))
        lt = self.lt('STWR')
        undo = self.lt('STWR/STWR')
        undo[0].replaced = lt
        do = self.lt('SP*')
        do[0].english = 'STWR STWR'
        do[0].is_retrospective_command = True
        do[0].replaced = undo
        self.assertTranslations(do)
        self.assertOutput(undo, do, None)

    def test_retrospective_delete_space(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('K', 'kick')
        self.define('B', 'back')
        self.define('SP*', '{*!}')
        self.translate(stroke('K'))
        self.translate(stroke('B'))
        self.translate(stroke('SP*'))
        undo = self.lt('K B')
        do = self.lt('SP*')
        do[0].english = 'kick{^~|^}back'
        do[0].is_retrospective_command = True
        do[0].replaced = undo
        self.assertTranslations(do)
        self.assertOutput(undo, do, None)

    def test_retrospective_delete_space_with_number(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('U', 'user')
        self.define('SP*', '{*!}')
        self.translate(stroke('U'))
        self.translate(stroke('1-'))
        self.translate(stroke('SP*'))
        undo = self.lt('U 1-')
        do = self.lt('SP*')
        do[0].english = 'user{^~|^}{&1}'
        do[0].is_retrospective_command = True
        do[0].replaced = undo
        self.assertTranslations(do)
        self.assertOutput(undo, do, None)

    def test_retrospective_delete_space_with_period(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('P-P', '{.}')
        self.define('SH*', 'zshrc')
        self.define('SP*', '{*!}')
        self.translate(stroke('P-P'))
        self.translate(stroke('SH*'))
        self.translate(stroke('SP*'))
        undo = self.lt('P-P SH*')
        do = self.lt('SP*')
        do[0].english = '{.}{^~|^}zshrc'
        do[0].is_retrospective_command = True
        do[0].replaced = undo
        self.assertTranslations(do)
        self.assertOutput(undo, do, None)

    def test_retrospective_toggle_asterisk(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('S', 'see')
        self.define('S*', 'sea')
        self.define('A*', '{*}')
        self.translate(stroke('S'))
        self.translate(stroke('A*'))
        undo = self.lt('S')
        do = self.lt('S*')
        self.assertTranslations(do)
        self.assertOutput(undo, do, None)

    def test_repeat_last_stroke1(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('TH', 'this')
        self.define('R*', '{*+}')
        self.translate(stroke('TH'))
        self.translate(stroke('R*'))
        undo = []
        do = self.lt('TH')
        state = self.lt('TH TH')
        self.assertTranslations(state)
        self.assertOutput(undo, do, do)

    def test_repeat_last_stroke2(self):
        self.define('T/E/S/T', 'a longer key')
        self.define('THA', 'that')
        self.define('R*', '{*+}')
        self.translate(stroke('THA'))
        self.translate(stroke('R*'))
        undo = []
        do = self.lt('THA')
        state = self.lt('THA THA')
        self.assertTranslations(state)
        self.assertOutput(undo, do, do)
예제 #40
0
파일: engine.py 프로젝트: ohAitch/plover
class StenoEngine(object):

    HOOKS = '''
    stroked
    translated
    machine_state_changed
    output_changed
    config_changed
    dictionaries_loaded
    send_string
    send_backspaces
    send_key_combination
    add_translation
    focus
    configure
    lookup
    quit
    '''.split()

    def __init__(self, config, keyboard_emulation):
        self._config = config
        self._is_running = False
        self._queue = Queue()
        self._lock = threading.RLock()
        self._machine = None
        self._machine_state = None
        self._machine_params = MachineParams(None, None, None)
        self._formatter = Formatter()
        self._formatter.set_output(self)
        self._formatter.add_listener(self._on_translated)
        self._translator = Translator()
        self._translator.add_listener(log.translation)
        self._translator.add_listener(self._formatter.format)
        self._dictionaries = self._translator.get_dictionary()
        self._dictionaries_manager = DictionaryLoadingManager()
        self._running_state = self._translator.get_state()
        self._keyboard_emulation = keyboard_emulation
        self._hooks = { hook: [] for hook in self.HOOKS }
        self._running_extensions = {}

    def __enter__(self):
        self._lock.__enter__()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self._lock.__exit__(exc_type, exc_value, traceback)

    def _in_engine_thread(self):
        raise NotImplementedError()

    def _same_thread_hook(self, func, *args, **kwargs):
        if self._in_engine_thread():
            func(*args, **kwargs)
        else:
            self._queue.put((func, args, kwargs))

    def run(self):
        while True:
            func, args, kwargs = self._queue.get()
            try:
                with self._lock:
                    if func(*args, **kwargs):
                        break
            except Exception:
                log.error('engine %s failed', func.__name__[1:], exc_info=True)

    def _stop(self):
        self._stop_extensions(self._running_extensions.keys())
        if self._machine is not None:
            self._machine.stop_capture()
            self._machine = None

    def _start(self):
        self._set_output(self._config.get_auto_start())
        self._update(full=True)

    def _set_dictionaries(self, dictionaries):
        def dictionaries_changed(l1, l2):
            if len(l1) != len(l2):
                return True
            for d1, d2 in zip(l1, l2):
                if d1 is not d2:
                    return True
            return False
        if not dictionaries_changed(dictionaries, self._dictionaries.dicts):
            # No change.
            return
        self._dictionaries = StenoDictionaryCollection(dictionaries)
        self._translator.set_dictionary(self._dictionaries)
        self._trigger_hook('dictionaries_loaded', self._dictionaries)

    def _update(self, config_update=None, full=False, reset_machine=False):
        original_config = self._config.as_dict()
        # Update configuration.
        if config_update is not None:
            self._config.update(**config_update)
            config = self._config.as_dict()
        else:
            config = original_config
        # Create configuration update.
        if full:
            config_update = config
        else:
            config_update = {
                option: value
                for option, value in config.items()
                if value != original_config[option]
            }
            if 'machine_type' in config_update:
                for opt in (
                    'machine_specific_options',
                    'system_keymap',
                ):
                    config_update[opt] = config[opt]
        # Update logging.
        log.set_stroke_filename(config['log_file_name'])
        log.enable_stroke_logging(config['enable_stroke_logging'])
        log.enable_translation_logging(config['enable_translation_logging'])
        # Update output.
        self._formatter.set_space_placement(config['space_placement'])
        self._formatter.start_attached = config['start_attached']
        self._formatter.start_capitalized = config['start_capitalized']
        self._translator.set_min_undo_length(config['undo_levels'])
        # Update system.
        system_name = config['system_name']
        if system.NAME != system_name:
            log.info('loading system: %s', system_name)
            system.setup(system_name)
        # Update machine.
        update_keymap = False
        start_machine = False
        machine_params = MachineParams(config['machine_type'],
                                       config['machine_specific_options'],
                                       config['system_keymap'])
        # Do not reset if only the keymap changed.
        if self._machine_params is None or \
           self._machine_params.type != machine_params.type or \
           self._machine_params.options != machine_params.options:
            reset_machine = True
        if reset_machine:
            if self._machine is not None:
                self._machine.stop_capture()
                self._machine = None
            machine_type = config['machine_type']
            machine_options = config['machine_specific_options']
            try:
                machine_class = registry.get_plugin('machine', machine_type).obj
            except Exception as e:
                raise InvalidConfigurationError(str(e))
            log.info('setting machine: %s', machine_type)
            self._machine = machine_class(machine_options)
            self._machine.set_suppression(self._is_running)
            self._machine.add_state_callback(self._machine_state_callback)
            self._machine.add_stroke_callback(self._machine_stroke_callback)
            self._machine_params = machine_params
            update_keymap = True
            start_machine = True
        elif self._machine is not None:
            update_keymap = 'system_keymap' in config_update
        if update_keymap:
            machine_keymap = config['system_keymap']
            if machine_keymap is not None:
                self._machine.set_keymap(machine_keymap)
        if start_machine:
            self._machine.start_capture()
        # Update running extensions.
        enabled_extensions = config['enabled_extensions']
        running_extensions = set(self._running_extensions)
        self._stop_extensions(running_extensions - enabled_extensions)
        self._start_extensions(enabled_extensions - running_extensions)
        # Trigger `config_changed` hook.
        if config_update:
            self._trigger_hook('config_changed', config_update)
        # Update dictionaries.
        config_dictionaries = OrderedDict(
            (d.path, d)
            for d in config['dictionaries']
        )
        copy_default_dictionaries(config_dictionaries.keys())
        # Start by unloading outdated dictionaries.
        self._dictionaries_manager.unload_outdated()
        self._set_dictionaries([
            d for d in self._dictionaries.dicts
            if d.path in config_dictionaries and \
               d.path in self._dictionaries_manager
        ])
        # And then (re)load all dictionaries.
        dictionaries = []
        for result in self._dictionaries_manager.load(config_dictionaries.keys()):
            if isinstance(result, DictionaryLoaderException):
                d = ErroredDictionary(result.path, result.exception)
                # Only show an error if it's new.
                if d != self._dictionaries.get(result.path):
                    log.error('loading dictionary `%s` failed: %s',
                              shorten_path(result.path), str(result.exception))
            else:
                d = result
            d.enabled = config_dictionaries[d.path].enabled
            dictionaries.append(d)
        self._set_dictionaries(dictionaries)

    def _start_extensions(self, extension_list):
        for extension_name in extension_list:
            log.info('starting `%s` extension', extension_name)
            try:
                extension = registry.get_plugin('extension', extension_name).obj(self)
                extension.start()
            except Exception:
                log.error('initializing extension `%s` failed', extension_name, exc_info=True)
            else:
                self._running_extensions[extension_name] = extension

    def _stop_extensions(self, extension_list):
        for extension_name in list(extension_list):
            log.info('stopping `%s` extension', extension_name)
            extension = self._running_extensions.pop(extension_name)
            extension.stop()
            del extension

    def _quit(self, code):
        self._stop()
        self.code = code
        self._trigger_hook('quit')
        return True

    def _toggle_output(self):
        self._set_output(not self._is_running)

    def _set_output(self, enabled):
        if enabled == self._is_running:
            return
        self._is_running = enabled
        if enabled:
            self._translator.set_state(self._running_state)
        else:
            self._translator.clear_state()
        if self._machine is not None:
            self._machine.set_suppression(enabled)
        self._trigger_hook('output_changed', enabled)

    def _machine_state_callback(self, machine_state):
        self._same_thread_hook(self._on_machine_state_changed, machine_state)

    def _machine_stroke_callback(self, steno_keys):
        self._same_thread_hook(self._on_stroked, steno_keys)

    @with_lock
    def _on_machine_state_changed(self, machine_state):
        assert machine_state is not None
        self._machine_state = machine_state
        machine_type = self._config.get_machine_type()
        self._trigger_hook('machine_state_changed', machine_type, machine_state)

    def _consume_engine_command(self, command):
        # The first commands can be used whether plover has output enabled or not.
        if command == 'RESUME':
            self._set_output(True)
            return True
        elif command == 'TOGGLE':
            self._toggle_output()
            return True
        elif command == 'QUIT':
            self.quit()
            return True
        if not self._is_running:
            return False
        # These commands can only be run when plover has output enabled.
        if command == 'SUSPEND':
            self._set_output(False)
        elif command == 'CONFIGURE':
            self._trigger_hook('configure')
        elif command == 'FOCUS':
            self._trigger_hook('focus')
        elif command == 'ADD_TRANSLATION':
            self._trigger_hook('add_translation')
        elif command == 'LOOKUP':
            self._trigger_hook('lookup')
        else:
            command_args = command.split(':', 1)
            command_fn = registry.get_plugin('command', command_args[0]).obj
            command_fn(self, command_args[1] if len(command_args) == 2 else '')
        return False

    def _on_stroked(self, steno_keys):
        stroke = Stroke(steno_keys)
        log.stroke(stroke)
        self._translator.translate(stroke)
        self._trigger_hook('stroked', stroke)

    def _on_translated(self, old, new):
        if not self._is_running:
            return
        self._trigger_hook('translated', old, new)

    def send_backspaces(self, b):
        if not self._is_running:
            return
        self._keyboard_emulation.send_backspaces(b)
        self._trigger_hook('send_backspaces', b)

    def send_string(self, s):
        if not self._is_running:
            return
        self._keyboard_emulation.send_string(s)
        self._trigger_hook('send_string', s)

    def send_key_combination(self, c):
        if not self._is_running:
            return
        self._keyboard_emulation.send_key_combination(c)
        self._trigger_hook('send_key_combination', c)

    def send_engine_command(self, command):
        suppress = not self._is_running
        suppress &= self._consume_engine_command(command)
        if suppress:
            self._machine.suppress_last_stroke(self._keyboard_emulation.send_backspaces)

    def toggle_output(self):
        self._same_thread_hook(self._toggle_output)

    def set_output(self, enabled):
        self._same_thread_hook(self._set_output, enabled)

    @property
    @with_lock
    def machine_state(self):
        return self._machine_state

    @property
    @with_lock
    def output(self):
        return self._is_running

    @output.setter
    def output(self, enabled):
        self._same_thread_hook(self._set_output, enabled)

    @property
    @with_lock
    def config(self):
        return self._config.as_dict()

    @config.setter
    def config(self, update):
        self._same_thread_hook(self._update, config_update=update)

    def reset_machine(self):
        self._same_thread_hook(self._update, reset_machine=True)

    def load_config(self):
        try:
            with open(self._config.target_file, 'rb') as f:
                self._config.load(f)
        except Exception:
            log.error('loading configuration failed, reseting to default', exc_info=True)
            self._config.clear()
            return False
        return True

    def start(self):
        self._same_thread_hook(self._start)

    def quit(self, code=0):
        # We need to go through the queue, even when already called
        # from the engine thread so _quit's return code does break
        # the thread out of its main loop.
        self._queue.put((self._quit, (code,), {}))

    def restart(self):
        self.quit(-1)

    def join(self):
        return self.code

    @with_lock
    def machine_specific_options(self, machine_type):
        return self._config.get_machine_specific_options(machine_type)

    @with_lock
    def system_keymap(self, machine_type, system_name):
        return self._config.get_system_keymap(machine_type, system_name)

    @with_lock
    def lookup(self, translation):
        return self._dictionaries.lookup(translation)

    @with_lock
    def raw_lookup(self, translation):
        return self._dictionaries.raw_lookup(translation)

    @with_lock
    def reverse_lookup(self, translation):
        matches = self._dictionaries.reverse_lookup(translation)
        return [] if matches is None else matches

    @with_lock
    def casereverse_lookup(self, translation):
        matches = self._dictionaries.casereverse_lookup(translation)
        return set() if matches is None else matches

    @with_lock
    def add_dictionary_filter(self, dictionary_filter):
        self._dictionaries.add_filter(dictionary_filter)

    @with_lock
    def remove_dictionary_filter(self, dictionary_filter):
        self._dictionaries.remove_filter(dictionary_filter)

    @with_lock
    def get_suggestions(self, translation):
        return Suggestions(self._dictionaries).find(translation)

    @property
    @with_lock
    def translator_state(self):
        return self._translator.get_state()

    @translator_state.setter
    @with_lock
    def translator_state(self, state):
        self._translator.set_state(state)

    @with_lock
    def clear_translator_state(self, undo=False):
        if undo:
            state = self._translator.get_state()
            self._formatter.format(state.translations, (), None)
        self._translator.clear_state()

    @property
    @with_lock
    def starting_stroke_state(self):
        return StartingStrokeState(self._formatter.start_attached,
                                   self._formatter.start_capitalized)

    @starting_stroke_state.setter
    @with_lock
    def starting_stroke_state(self, state):
        self._formatter.start_attached = state.attach
        self._formatter.start_capitalized = state.capitalize

    @with_lock
    def add_translation(self, strokes, translation, dictionary_path=None):
        if dictionary_path is None:
            dictionary_path = self._dictionaries.first_writable().path
        self._dictionaries.set(strokes, translation, path=dictionary_path)
        self._dictionaries.save(path_list=(dictionary_path,))

    @property
    @with_lock
    def dictionaries(self):
        return self._dictionaries

    # Hooks.

    def _trigger_hook(self, hook, *args, **kwargs):
        for callback in self._hooks[hook]:
            try:
                callback(*args, **kwargs)
            except Exception:
                log.error('hook %r callback %r failed',
                          hook, callback,
                          exc_info=True)

    @with_lock
    def hook_connect(self, hook, callback):
        self._hooks[hook].append(callback)

    @with_lock
    def hook_disconnect(self, hook, callback):
        self._hooks[hook].remove(callback)
예제 #41
0
def test_reverse_lookup():
    dc = StenoDictionaryCollection()

    d1 = StenoDictionary()
    d1['PWAOUFL'] = 'beautiful'
    d1['WAOUFL'] = 'beautiful'

    d2 = StenoDictionary()
    d2['PW-FL'] = 'beautiful'

    d3 = StenoDictionary()
    d3['WAOUFL'] = 'not beautiful'

    # Simple test.
    dc.set_dicts([d1])
    assert dc.reverse_lookup('beautiful') == {'PWAOUFL', 'WAOUFL'}

    # No duplicates.
    d2_copy = StenoDictionary()
    d2_copy.update(d2)
    dc.set_dicts([d2_copy, d2])
    assert dc.reverse_lookup('beautiful') == {'PW-FL'}

    # Don't stop at the first dictionary with matches.
    dc.set_dicts([d2, d1])
    assert dc.reverse_lookup('beautiful') == {'PW-FL', 'PWAOUFL', 'WAOUFL'}

    # Ignore keys overridden by a higher precedence dictionary.
    dc.set_dicts([d3, d2, d1])
    assert dc.reverse_lookup('beautiful') == {'PW-FL', 'PWAOUFL'}