diff options
-rw-r--r-- | ranger/core/fm.py | 4 | ||||
-rw-r--r-- | ranger/ext/signal_dispatcher.py | 79 | ||||
-rw-r--r-- | test/tc_signal.py | 79 |
3 files changed, 161 insertions, 1 deletions
diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 94d0d85d..aef10150 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -26,18 +26,20 @@ from ranger.container import Bookmarks from ranger.core.runner import Runner from ranger import relpath_conf from ranger.ext.get_executables import get_executables +from ranger.ext.signal_dispatcher import SignalDispatcher from ranger import __version__ from ranger.fsobject import Loader CTRL_C = 3 TICKS_BEFORE_COLLECTING_GARBAGE = 100 -class FM(Actions): +class FM(Actions, SignalDispatcher): input_blocked = False input_blocked_until = 0 def __init__(self, ui=None, bookmarks=None, tags=None): """Initialize FM.""" Actions.__init__(self) + SignalDispatcher.__init__(self) self.ui = ui self.log = deque(maxlen=20) self.bookmarks = bookmarks diff --git a/ranger/ext/signal_dispatcher.py b/ranger/ext/signal_dispatcher.py new file mode 100644 index 00000000..542fb472 --- /dev/null +++ b/ranger/ext/signal_dispatcher.py @@ -0,0 +1,79 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +class Signal(dict): + stopped = False + def __init__(self, **keywords): + dict.__init__(self, keywords) + self.__dict__ = self + + def stop(self): + self.stopped = True + + +class SignalHandler(object): + active = True + def __init__(self, signal_name, function, priority, pass_signal): + self.priority = max(0, min(1, priority)) + self.signal_name = signal_name + self.function = function + self.pass_signal = pass_signal + + +class SignalDispatcher(object): + def __init__(self): + self._signals = dict() + + signal_clear = __init__ + + def signal_bind(self, signal_name, function, priority=0.5): + assert isinstance(signal_name, str) + try: + handlers = self._signals[signal_name] + except: + handlers = self._signals[signal_name] = [] + nargs = function.__code__.co_argcount - hasattr(function, 'im_func') + handler = SignalHandler(signal_name, function, priority, nargs > 0) + handlers.append(handler) + handlers.sort(key=lambda handler: -handler.priority) + return handler + + def signal_unbind(self, signal_handler): + try: + handlers = self._signals[signal_handler.signal_name] + except KeyError: + pass + else: + handlers.remove(signal_handler) + + def signal_emit(self, signal_name, **kw): + assert isinstance(signal_name, str) + try: + handlers = self._signals[signal_name] + except: + return + if not handlers: + return + + signal = Signal(origin=self, name=signal_name, **kw) + + for handler in handlers: # propagate + if handler.active: + if handler.pass_signal: + handler.function(signal) + else: + handler.function() + if signal.stopped: + return diff --git a/test/tc_signal.py b/test/tc_signal.py new file mode 100644 index 00000000..67625505 --- /dev/null +++ b/test/tc_signal.py @@ -0,0 +1,79 @@ +# Copyright (C) 2009, 2010 Roman Zimbelmann <romanz@lavabit.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +if __name__ == '__main__': from __init__ import init; init() +import unittest +from ranger.ext.signal_dispatcher import SignalDispatcher + +class TestSignal(unittest.TestCase): + def setUp(self): + self.sd = SignalDispatcher() + + def test_signal_register_emit(self): + sd = self.sd + def poo(sig): + self.assert_('works' in sig) + self.assertEqual('yes', sig.works) + handler = sd.signal_bind('x', poo) + + sd.signal_emit('x', works='yes') + sd.signal_unbind(handler) + sd.signal_emit('x') + + def test_signal_order(self): + sd = self.sd + lst = [] + def addn(n): + return lambda _: lst.append(n) + + sd.signal_bind('x', addn(6)) + sd.signal_bind('x', addn(3), priority=1) + sd.signal_bind('x', addn(2), priority=1) + sd.signal_bind('x', addn(9), priority=0) + sd.signal_bind('x', addn(1337), priority=0.7) + sd.signal_emit('x') + + self.assert_(lst.index(3) < lst.index(6)) + self.assert_(lst.index(2) < lst.index(6)) + self.assert_(lst.index(6) < lst.index(9)) + self.assert_(lst.index(1337) < lst.index(6)) + self.assert_(lst.index(1337) < lst.index(9)) + self.assert_(lst.index(1337) > lst.index(2)) + + def test_modifying_arguments(self): + sd = self.sd + lst = [] + def modify(s): + s.number = 5 + def set_number(s): + lst.append(s.number) + def stopit(s): + s.stop() + + sd.signal_bind('setnumber', set_number) + sd.signal_emit('setnumber', number=100) + self.assertEqual(100, lst[-1]) + + sd.signal_bind('setnumber', modify, priority=1) + sd.signal_emit('setnumber', number=100) + self.assertEqual(5, lst[-1]) + + lst.append(None) + sd.signal_bind('setnumber', stopit, priority=1) + sd.signal_emit('setnumber', number=100) + self.assertEqual(None, lst[-1]) + +if __name__ == '__main__': + unittest.main() |