Coverage for src/twofas/cli_support.py: 100%
26 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-01-29 11:16 +0100
« prev ^ index » next coverage.py v7.4.1, created at 2024-01-29 11:16 +0100
1"""
2This file contains helpers for the cli.
3"""
5import os
6import typing
8import configuraptor
9import questionary
10from configuraptor import beautify, postpone
11from typing_extensions import Never
13from .cli_settings import CliSettings
16@beautify
17class AppState(configuraptor.TypedConfig, configuraptor.Singleton):
18 """
19 Global state (settings from config + run-specific variables such as --verbose).
20 """
22 verbose: bool = False
23 settings: CliSettings = postpone()
26state = AppState.load({})
28P = typing.ParamSpec("P")
29R = typing.TypeVar("R")
32@typing.overload
33def clear(fn: typing.Callable[P, R]) -> typing.Callable[P, R]:
34 """
35 When calling clear with parens, you get the same callable back.
36 """
39@typing.overload
40def clear(fn: None = None) -> typing.Callable[[typing.Callable[P, R]], typing.Callable[P, R]]:
41 """
42 When calling clear without parens, you'll get the same callable back later.
43 """
46def clear(
47 fn: typing.Callable[P, R] | None = None
48) -> typing.Callable[P, R] | typing.Callable[[typing.Callable[P, R]], typing.Callable[P, R]]: # pragma: no cover
49 """
50 Clear the screen before executing a function.
52 Examples:
53 @clear
54 def some_fun(): ...
56 @clear()
57 def other_func(): ...
58 """
59 if fn:
61 def inner(*args: P.args, **kwargs: P.kwargs) -> R:
62 os.system("clear") # nosec: B605 B607
63 return fn(*args, **kwargs)
65 return inner
66 else:
67 return clear
70@clear
71def exit_with_clear(status_code: int) -> Never: # pragma: no cover
72 """
73 First clear the screen with the @clear decorator, then exit with a specific exit code.
74 """
75 exit(status_code)
78def generate_custom_style(
79 main_color: str = "green", # "#673ab7"
80 secondary_color: str = "#673ab7", # "#f44336"
81) -> questionary.Style:
82 """
83 Reusable questionary style for all prompts of this tool.
85 Primary and secondary color can be changed, other styles stay the same for consistency.
86 """
87 return questionary.Style(
88 [
89 ("qmark", f"fg:{main_color} bold"), # token in front of the question
90 ("question", "bold"), # question text
91 ("answer", f"fg:{secondary_color} bold"), # submitted answer text behind the question
92 ("pointer", f"fg:{main_color} bold"), # pointer used in select and checkbox prompts
93 ("highlighted", f"fg:{main_color} bold"), # pointed-at choice in select and checkbox prompts
94 ("selected", "fg:#cc5454"), # style for a selected item of a checkbox
95 ("separator", "fg:#cc5454"), # separator in lists
96 ("instruction", ""), # user instructions for select, rawselect, checkbox
97 ("text", ""), # plain text
98 ("disabled", "fg:#858585 italic"), # disabled choices for select and checkbox prompts
99 ]
100 )
103def generate_choices(choices: dict[str, str], with_exit: bool = True) -> list[questionary.Choice]:
104 """
105 Turn a dict of label -> value items into a list of Choices with an automatic shortcut key (1 - 9).
107 If with_exit is True, an option with shortcut key 0 will be added to quit the program.
108 """
109 result = [
110 questionary.Choice(key, value, shortcut_key=str(idx)) for idx, (key, value) in enumerate(choices.items(), 1)
111 ]
113 if with_exit:
114 result.append(questionary.Choice("Exit", "exit", shortcut_key="0"))
116 return result