aboutsummaryrefslogtreecommitdiff
path: root/app/backends/setup.py
blob: bd43b92d3bca5de54bc331c2f202100bad6d7c5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""Shared entry-point plumbing for the backend setup wizards.

Every backend module exposes the same surface — ``_wizard`` (TUI screens),
``_execute_steps``/``_execute`` (the work, as task-view steps),
``setup_screen`` (hub-embedded flow), ``run_tui`` (standalone curses
flow), ``main`` (CLI) — and this module holds the parts of that surface
that are identical everywhere. The backend keeps the thin named wrappers
so its public names (and test seams) stay on the backend module.
"""

import sys

from ui import taskview, tui


def interactive() -> bool:
    """True when the TUI wizard can run (curses importable + tty)."""
    try:
        import curses  # noqa: F401
    except ImportError:
        return False
    try:
        return sys.stdin.isatty() and sys.stdout.isatty()
    except (AttributeError, ValueError):
        return False


def screen_flow(stdscr, *, wizard, steps_of, title,
                parser_factory) -> int:
    """Run the setup wizard on an existing curses screen (the hub's).

    The hub drives this as one screen of its own ``tui.Wizard`` stack, so
    Esc on the wizard's first screen simply returns here and the hub pops
    back to the menu that launched it. The setup tail runs inside the TUI
    task view on this same screen, so the hub's curses session stays intact
    and the user sees per-step status instead of being dropped to the
    console. Returns 0 on completion, 1 when the user aborted.

    WIZARD is ``(stdscr, args) -> settings | None``; STEPS_OF turns the
    settings into task-view steps; PARSER_FACTORY builds the argparse
    parser whose empty namespace seeds the wizard.
    """
    args = parser_factory().parse_args([])
    settings = wizard(stdscr, args)
    if settings is None:
        return 1
    return taskview.run_steps(stdscr, title, steps_of(settings))


def tui_flow(wizard, execute, *, args, execute_takes_args=False,
             aborted_message="[INFO] Aborted") -> int:
    """Run the setup wizard end-to-end in its own curses session.

    WIZARD is ``(args) -> settings | None`` via ``curses.wrapper``;
    EXECUTE performs the settings (called with ARGS too when
    EXECUTE_TAKES_ARGS). Restores the text cursor afterwards and reports
    cancellation/abort consistently. Returns the process exit code.
    """
    import curses
    try:
        settings = curses.wrapper(wizard, args)
    except tui.WizardCancelled:
        print("\n[INFO] Cancelled; nothing was written")
        return 1
    try:
        curses.curs_set(1)
    except curses.error:
        pass
    if settings is None:
        print(aborted_message)
        return 1
    return execute(settings) if not execute_takes_args \
        else execute(settings, args)