| """What happens when somebody double-clicks the executable. |
| |
| **One decision, stated plainly: no arguments means the screen, not the usage |
| text.** A command-line program with no arguments conventionally prints how to |
| use it and exits, and that convention is right for a program invoked from a |
| shell by somebody who mistyped. It is wrong for this one, because the most |
| common way this is started is a double-click by a person who has never seen a |
| terminal, and answering them with an argument grammar is answering a question |
| they did not ask. |
| |
| So: no arguments at all, and a console that can be read, opens the setup and |
| then the screen. Any argument at all, or a console that cannot be read, behaves |
| exactly as before. Nobody's script changes, and `--help` still prints help. |
| |
| **The distinction that makes this safe** is that this module produces a command |
| line and hands it to `cli.main`. It does not have its own way to start a worker. |
| Everything `main` refuses to do when typed at, it refuses to do here: a remote |
| server without permission, a plaintext one without a second permission, a |
| missing pairing code. This file asks questions and formats an answer; it decides |
| nothing about what a worker may do. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
| import sys |
| from typing import Any, Callable, Optional, Sequence |
|
|
| |
| |
| |
| FORCE_ENV_VAR = "DISTINCT_DESKTOP" |
|
|
|
|
| def wants_desktop( |
| argv: Optional[Sequence[str]] = None, |
| *, |
| environ: Optional[dict] = None, |
| isatty: Optional[Callable[[], bool]] = None, |
| ) -> bool: |
| """Whether this invocation should open the screen rather than parse flags. |
| |
| Three things have to be true, and the third is the one that matters. There |
| must be no arguments, because an argument is somebody being specific and |
| being overridden is infuriating. The console must be readable, because the |
| setup screen asks questions and a screen that asks a question nobody can |
| answer is a hang. And the environment override must not say otherwise, |
| which is how a test drives either path deliberately. |
| """ |
|
|
| environ = os.environ if environ is None else environ |
| forced = environ.get(FORCE_ENV_VAR) |
| if forced == "0": |
| return False |
| if forced == "1": |
| return True |
|
|
| arguments = list(sys.argv[1:] if argv is None else argv) |
| if arguments: |
| return False |
| if isatty is None: |
| def isatty() -> bool: |
| try: |
| return bool(sys.stdin.isatty() and sys.stdout.isatty()) |
| except (AttributeError, ValueError): |
| return False |
| return bool(isatty()) |
|
|
|
|
| def run(*, main: Optional[Callable[..., int]] = None, settings_io: Any = None) -> int: |
| """Ask, remember, and start. Returns the exit code the worker returns. |
| |
| ``settings_io`` is the `firstrun` module by default and is injectable so a |
| test can drive the whole flow against a temporary file rather than against |
| whatever is in the person's real configuration directory. |
| """ |
|
|
| from . import firstrun |
| from .dashboard import ActivityLog |
| from .tui import SetupApp |
|
|
| settings_io = settings_io or firstrun |
| if main is None: |
| from .cli import main as main |
|
|
| settings = settings_io.load() |
| answered = SetupApp(settings, first_run=not settings.is_complete()).run() |
| if answered is None: |
| print("Nothing was started.", file=sys.stderr) |
| return 0 |
| settings, code = answered |
| if not code: |
| print("No pairing code, so nothing was started.", file=sys.stderr) |
| return 0 |
|
|
| where = settings_io.save(settings) |
| if where is None: |
| print( |
| "These answers could not be saved, so they will be asked again next " |
| "time. The worker still starts.", |
| file=sys.stderr, |
| ) |
|
|
| argv = settings_io.to_argv(settings, code) + ["--dashboard"] |
|
|
| |
| |
| |
| |
| |
| log = ActivityLog(mirror=sys.stderr) |
| log._distinct_activity_log = log |
| previous = sys.stderr |
| sys.stderr = log |
| try: |
| return int(main(argv)) |
| finally: |
| sys.stderr = previous |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|