diff options
Diffstat (limited to 'app/ui/tui.py')
| -rw-r--r-- | app/ui/tui.py | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/app/ui/tui.py b/app/ui/tui.py index 95130cf..0a119e9 100644 --- a/app/ui/tui.py +++ b/app/ui/tui.py @@ -46,6 +46,48 @@ class WizardCancelled(Exception): """Raised when the user presses Esc to abort the wizard.""" +class Wizard: + """Drive a stack of screen closures with "Esc goes back one screen". + + Each screen is a zero-argument callable that shows exactly one + interactive screen and returns a navigation result: + + Wizard.BACK the user pressed Esc/q; go back one screen + a callable advance to that screen (it is the next screen) + None abort the whole wizard + any value finish the wizard and return that value (the settings) + + ``run(first_screen)`` returns the final value, or None when the user + pressed Esc on the first screen (or a screen returned None). Only + screens that actually render are pushed onto the stack, so Esc always + lands on the previous real screen; a step whose value is already known + (a flag, or a condition that does not apply) is folded into the screen + that precedes it and never appears on the stack, so it cannot be backed + into. + """ + + BACK = object() + + def __init__(self): + self._stack = [] + + def run(self, first_screen) -> Optional[object]: + screen = first_screen + while True: + nxt = screen() + if nxt is Wizard.BACK: + if not self._stack: + return None + screen = self._stack.pop() + continue + if nxt is None: + return None + if not callable(nxt): + return nxt + self._stack.append(screen) + screen = nxt + + @contextlib.contextmanager def suspend(scr): """Temporarily leave curses to run plain-console code. |
