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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
#!/usr/bin/python
"""
# Purpose
ListManager is a widget based on `menu` which allows the handling of repetitive operations in a list.
Imagine you have a list and want to add/copy/edit/delete their elements. With this widget you will be shown the list
```
Vamos alla
Use ESC to skip
> uno : 1
dos : 2
tres : 3
cuatro : 4
==>
Confirm and exit
Cancel
(Press "/" to search)
```
Once you select one of the elements of the list, you will be promted with the action to be done to the selected element
```
uno : 1
dos : 2
> tres : 3
cuatro : 4
==>
Confirm and exit
Cancel
(Press "/" to search)
Select an action for < {'tres': 3} >
Add
Copy
Edit
Delete
> Cancel
```
You execute the action for this element (which might or not involve user interaction) and return to the list main page
till you call one of the options `confirm and exit` which returns the modified list or `cancel` which returns the original list unchanged.
If the list is empty one action can be defined as default (usually Add). We call it **null_action**
YOu can also define a **default_action** which will appear below the separator, not tied to any element of the list. Barring explicit definition, default_action will be the null_action
```
==>
Add
Confirm and exit
Cancel
(Press "/" to search)
```
The default implementation can handle simple lists and a key:value dictionary. The default actions are the shown above.
A sample of basic usage is included at the end of the source.
More sophisticaded uses can be achieved by
* changing the action list and the null_action during intialization
```
opciones = ListManager('Vamos alla',opciones,[str(_('Add')),str(_('Delete'))],_('Add')).run()
```
* And using following methods to overwrite/define user actions and other details:
* * `reformat`. To change the appearance of the list elements
* * `action_list`. To modify the content of the action list once an element is defined. F.i. to avoid Delete to appear for certain elements, or to add/modify action based in the value of the element.
* * `exec_action` which contains the actual code to be executed when an action is selected
The contents in the base class of this methods serve for a very basic usage, and are to be taken as samples. Thus the best use of this class would be to subclass in your code
```
class ObjectList(archinstall.ListManager):
def __init__(prompt,list):
self.ObjectAction = [... list of actions ...]
self.ObjectNullAction = one ObjectAction
super().__init__(prompt,list,ObjectActions,ObjectNullAction)
def reformat(self):
... beautfy the output of the list
def action_list(self):
... if you need some changes to the action list based on self.target
def exec_action(self):
if self.action == self.ObjectAction[0]:
performFirstAction(self.target, ...)
...
resultList = ObjectList(prompt,originallist).run()
```
"""
from .text_input import TextInput
from .menu import Menu
from os import system
from copy import copy
from typing import Union
class ListManager:
def __init__(self,prompt :str, base_list :Union[list,dict] ,base_actions :list = None,null_action :str = None, default_action :Union[str,list] = None, header :Union[str,list] = None):
"""
param :prompt Text which will appear at the header
type param: string | DeferredTranslation
param :base:_list list/dict of option to be shown / mainpulated
type param: list | dict
param base_actions an alternate list of actions to the items of the object
type param: list
param: null_action action which will be taken (if any) when base_list is empty
type param: string
param: default_action action which will be presented at the bottom of the list. Shouldn't need a target. If not present, null_action is set there.
Both Null and Default actions can be defined outside the base_actions list, as long as they are launched in exec_action
type param: string or list
param: header one or more header lines for the list
type param: string or list
"""
explainer = str(_('\n Choose an object from the list, and select one of the available actions for it to execute'))
self.prompt = prompt + explainer if prompt else explainer
self.null_action = str(null_action) if null_action else None
if not default_action:
self.default_action = [self.null_action,]
elif isinstance(default_action,(list,tuple)):
self.default_action = default_action
else:
self.default_action = [str(default_action),]
self.header = header if header else None
self.cancel_action = str(_('Cancel'))
self.confirm_action = str(_('Confirm and exit'))
self.separator = '==>'
self.bottom_list = [self.confirm_action,self.cancel_action]
self.bottom_item = [self.cancel_action]
self.base_actions = base_actions if base_actions else [str(_('Add')),str(_('Copy')),str(_('Edit')),str(_('Delete'))]
self.base_data = base_list
self.data = copy(base_list) # as refs, changes are immediate
# default values for the null case
self.target = None
self.action = self.null_action
if len(self.data) == 0 and self.null_action:
self.exec_action()
def run(self):
while True:
self.data_formatted = self.reformat()
options = self.data_formatted + [self.separator]
if self.default_action:
options += self.default_action
options += self.bottom_list
system('clear')
target = Menu(self.prompt,
options,
sort=False,
clear_screen=False,
clear_menu_on_exit=False,
header=self.header).run()
if not target or target in self.bottom_list:
break
if target and target == self.separator:
continue
if target and target in self.default_action:
self.action = target
target = None
self.target = None
self.exec_action()
continue
if isinstance(self.data,dict):
key = list(self.data.keys())[self.data_formatted.index(target)]
self.target = {key: self.data[key]}
else:
self.target = self.data[self.data_formatted.index(target)]
# Possible enhacement. If run_actions returns false a message line indicating the failure
self.run_actions(target)
if not target or target == self.cancel_action: # TODO dubious
return self.base_data # return the original list
else:
return self.data
def run_actions(self,prompt_data=None):
options = self.action_list() + self.bottom_item
prompt = _("Select an action for < {} >").format(prompt_data if prompt_data else self.target)
self.action = Menu(prompt,
options,
sort=False,
skip=False,
clear_screen=False,
clear_menu_on_exit=False,
preset_values=self.bottom_item,
show_search_hint=False).run()
if self.action == self.cancel_action:
return False
else:
return self.exec_action()
"""
The following methods are expected to be overwritten by the user if the needs of the list are beyond the simple case
"""
def reformat(self):
"""
method to get the data in a format suitable to be shown
It is executed once for run loop and processes the whole self.data structure
"""
if isinstance(self.data,dict):
return list(map(lambda x:f"{x} : {self.data[x]}",self.data))
else:
return list(map(lambda x:str(x),self.data))
def action_list(self):
"""
can define alternate action list or customize the list for each item.
Executed after any item is selected, contained in self.target
"""
return self.base_actions
def exec_action(self):
"""
what's executed one an item (self.target) and one action (self.action) is selected.
Should be overwritten by the user
The result is expected to update self.data in this routine, else it is ignored
The basic code is useful for simple lists and dictionaries (key:value pairs, both strings)
"""
# TODO guarantee unicity
if isinstance(self.data,list):
if self.action == str(_('Add')):
self.target = TextInput(_('Add :'),None).run()
self.data.append(self.target)
if self.action == str(_('Copy')):
while True:
target = TextInput(_('Copy to :'),self.target).run()
if target != self.target:
self.data.append(self.target)
break
elif self.action == str(_('Edit')):
tgt = self.target
idx = self.data.index(self.target)
result = TextInput(_('Edite :'),tgt).run()
self.data[idx] = result
elif self.action == str(_('Delete')):
del self.data[self.data.index(self.target)]
elif isinstance(self.data,dict):
# allows overwrites
if self.target:
origkey,origval = list(self.target.items())[0]
else:
origkey = None
origval = None
if self.action == str(_('Add')):
key = TextInput(_('Key :'),None).run()
value = TextInput(_('Value :'),None).run()
self.data[key] = value
if self.action == str(_('Copy')):
while True:
key = TextInput(_('Copy to new key:'),origkey).run()
if key != origkey:
self.data[key] = origval
break
elif self.action == str(_('Edit')):
value = TextInput(_(f'Edit {origkey} :'),origval).run()
self.data[origkey] = value
elif self.action == str(_('Delete')):
del self.data[origkey]
return True
if __name__ == "__main__":
# opciones = ['uno','dos','tres','cuatro']
# opciones = archinstall.list_mirrors()
opciones = {'uno':1,'dos':2,'tres':3,'cuatro':4}
acciones = ['editar','borrar','añadir']
cabecera = ["En Jaen Donde Resido","Vive don Lope de Sosa"]
opciones = ListManager('Vamos alla',opciones,None,_('Add'),default_action=acciones,header=cabecera).run()
print(opciones)
|