Source code for thurible.eventmanager

"""
eventmanager
~~~~~~~~~~~~

A manager that uses events sent from the user interface to drive
application flow.
"""
from queue import Queue
from threading import Thread
from typing import Callable, Mapping, Optional

import thurible.messages as tm
from thurible.panel import Panel
from thurible.thurible import queued_manager
from thurible.util import get_queues


# Types.
EventScript = Callable[[tm.Message, Queue], bool]
EventMap = Mapping[type, EventScript]


# Manager.
[docs] def event_manager( event_map: Optional[EventMap] = None, initial_panel: Optional[Panel] = None ) -> None: """Manage a terminal display by mapping :ref:`response-messages` to event scripts (see below). :param event_map: (Optional.) A :class:`dict` mapping the :class:`thurible.panel.Message` types managers can send to applications (:ref:`response-messages`) to functions in your application. These functions must accept a :class:`queue.Queue` object and the response message as parameters. It must return a :class:`bool` indicating whether the application should continue running. :param initial_panel: (Optional.) The first panel displayed in the terminal. While this is technically optional, that's just for testing purposes. You should really provide this to the manager. The panel passed this way will be stored as "__init". :return: None :rtype: NoneType :usage: An example small application that uses the :func:`thurible.event_manager` show a splash screen then quit if a button is pressed:: from thurible import event_manager, Splash import thurible.messages as tm # Create the event handlers. def data_handler(msg, q_to): msg = tm.End('Quitting.') q_to.put(msg) return False def ending_handler(msg, q_to): if msg.exception: raise msg.exception return False # Map the handlers to event messages. event_map = { tm.Data: data_handler, tm.Ending: ending_handler, } # Create the panel to display when the manager starts. splash = Splash('SPAM!') # Run the event_manager. event_manager(event_map, splash) :event scripts: An :dfn:`event script` is a function that: * Accepts a :ref:`response message<response-messages>` it will receive from the manager and a :class:`queue.Queue` for it to send :ref:`command messages<command-messages>` to the manager. * Returns `True` if the manager should continue running. * Returns `False` if the manager should end. For example, let's say we want an event script that will handle user input. It will: * Display a splash screen if the user presses `x`, * Display a different splash screen if the user presses `y`, * End the :func:`event_manager` if the user presses the space bar. That would look like:: import thurible from thurible import messages as msgs def data_handler(data, q_to): keep_running = True # If the user presses `x` display the X screen. if data.value == 'x': splash = thurible.Splash('XXXXX') store_msg = msgs.Store('x', splash) q_to.put(store_msg) show_msg = msgs.Show('x') q_to.put(show_msg) # If the user presses `y` display the Y screen. if data.value == 'y': splash = thurible.Splash('YYYYY') store_msg = msgs.Store('y', splash) q_to.put(store_msg) show_msg = msgs.Show('y') q_to.put(show_msg) # If the user presses ` ` end. if data.value == ' ': keep_running = False return keep_running These event scripts are intended for fairly simple use cases. They respond to a single message from the manager. They can add messages to the manager's input queue to tell the manager to act. They can tell the manager to end. If you need more complex behaviors like checking the manager's state or maintaining internal state, you probably should use :func:`queued_manager` directly rather than :func:`event_manager`. """ if not event_map: event_map = {} q_to, q_from = get_queues() T = Thread(target=queued_manager, args=(q_to, q_from)) run = True try: T.start() if initial_panel: q_to.put(tm.Store('__init', initial_panel)) q_to.put(tm.Show('__init')) while run: run = _check_for_message(q_to, q_from, event_map) except KeyboardInterrupt as ex: reason = 'Keyboard Interrupt' msg = tm.End(reason) q_to.put(msg) raise ex
# Private functions. def _check_for_message( q_to: Queue, q_from: Queue, event_map: EventMap ) -> bool: """Check for and handle UI messages.""" run = True if not q_from.empty(): msg = q_from.get() for msg_type in event_map: if isinstance(msg, msg_type) and isinstance(msg, tm.Message): run = event_map[msg_type](msg, q_to) break else: if isinstance(msg, tm.Ending): run = False return run