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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
import copy
from os import system
from typing import Any, TYPE_CHECKING, Dict, Optional, Tuple, List
from .menu import Menu
from ..output import FormattedOutput
if TYPE_CHECKING:
_: Any
class ListManager:
def __init__(
self,
prompt: str,
entries: List[Any],
base_actions: List[str],
sub_menu_actions: List[str]
):
"""
:param prompt: Text which will appear at the header
type param: string | DeferredTranslation
:param entries: list/dict of option to be shown / manipulated
type param: list
:param base_actions: list of actions that is displayed in the main list manager,
usually global actions such as 'Add...'
type param: list
:param sub_menu_actions: list of actions available for a chosen entry
type param: list
"""
self._original_data = copy.deepcopy(entries)
self._data = copy.deepcopy(entries)
explainer = str(_('\n Choose an object from the list, and select one of the available actions for it to execute'))
self._prompt = prompt if prompt else explainer
self._separator = ''
self._confirm_action = str(_('Confirm and exit'))
self._cancel_action = str(_('Cancel'))
self._terminate_actions = [self._confirm_action, self._cancel_action]
self._base_actions = base_actions
self._sub_menu_actions = sub_menu_actions
self._last_choice: Optional[str] = None
@property
def last_choice(self) -> Optional[str]:
return self._last_choice
def is_last_choice_cancel(self) -> bool:
if self._last_choice is not None:
return self._last_choice == self._cancel_action
return False
def run(self) -> List[Any]:
while True:
# this will return a dictionary with the key as the menu entry to be displayed
# and the value is the original value from the self._data container
data_formatted = self.reformat(self._data)
options, header = self._prepare_selection(data_formatted)
system('clear')
choice = Menu(
self._prompt,
options,
sort=False,
clear_screen=False,
clear_menu_on_exit=False,
header=header,
skip_empty_entries=True,
skip=False,
show_search_hint=False
).run()
if choice.value in self._base_actions:
self._data = self.handle_action(choice.value, None, self._data)
elif choice.value in self._terminate_actions:
break
else: # an entry of the existing selection was chosen
selected_entry = data_formatted[choice.value] # type: ignore
self._run_actions_on_entry(selected_entry)
self._last_choice = choice.value # type: ignore
if choice.value == self._cancel_action:
return self._original_data # return the original list
else:
return self._data
def _prepare_selection(self, data_formatted: Dict[str, Any]) -> Tuple[List[str], str]:
# header rows are mapped to None so make sure
# to exclude those from the selectable data
options: List[str] = [key for key, val in data_formatted.items() if val is not None]
header = ''
if len(options) > 0:
table_header = [key for key, val in data_formatted.items() if val is None]
header = '\n'.join(table_header)
if len(options) > 0:
options.append(self._separator)
options += self._base_actions
options += self._terminate_actions
return options, header
def _run_actions_on_entry(self, entry: Any):
options = self.filter_options(entry, self._sub_menu_actions) + [self._cancel_action]
display_value = self.selected_action_display(entry)
prompt = _("Select an action for '{}'").format(display_value)
choice = Menu(
prompt,
options,
sort=False,
clear_screen=False,
clear_menu_on_exit=False,
show_search_hint=False
).run()
if choice.value and choice.value != self._cancel_action:
self._data = self.handle_action(choice.value, entry, self._data)
def reformat(self, data: List[Any]) -> Dict[str, Optional[Any]]:
"""
Default implementation of the table to be displayed.
Override if any custom formatting is needed
"""
table = FormattedOutput.as_table(data)
rows = table.split('\n')
# these are the header rows of the table and do not map to any User obviously
# we're adding 2 spaces as prefix because the menu selector '> ' will be put before
# the selectable rows so the header has to be aligned
display_data: Dict[str, Optional[Any]] = {f' {rows[0]}': None, f' {rows[1]}': None}
for row, entry in zip(rows[2:], data):
row = row.replace('|', '\\|')
display_data[row] = entry
return display_data
def selected_action_display(self, selection: Any) -> str:
"""
this will return the value to be displayed in the
"Select an action for '{}'" string
"""
raise NotImplementedError('Please implement me in the child class')
def handle_action(self, action: Any, entry: Optional[Any], data: List[Any]) -> List[Any]:
"""
this function is called when a base action or
a specific action for an entry is triggered
"""
raise NotImplementedError('Please implement me in the child class')
def filter_options(self, selection: Any, options: List[str]) -> List[str]:
"""
filter which actions to show for an specific selection
"""
return options
|