Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/menu/list_manager.py
blob: de18791cf6c281cb85575cdf03405ae86828a88f (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
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