From 65a5a335aa21ea44fd99fb200e238df54b3c2e47 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 18 May 2022 21:59:49 +1000 Subject: Enhance view (#1210) * Add preview for menu entries * Fix mypy * Update * Update * Fix mypy Co-authored-by: Daniel Girtler --- .github/workflows/mypy.yaml | 2 +- archinstall/lib/disk/blockdevice.py | 42 ++++++++++---- archinstall/lib/hsm/fido.py | 2 +- archinstall/lib/menu/global_menu.py | 66 +++++++++++++++++----- archinstall/lib/menu/list_manager.py | 4 +- archinstall/lib/menu/selection_menu.py | 8 ++- archinstall/lib/user_interaction/general_conf.py | 1 - archinstall/lib/user_interaction/network_conf.py | 12 ++-- .../lib/user_interaction/partitioning_conf.py | 19 ++++--- archinstall/lib/user_interaction/utils.py | 1 + 10 files changed, 110 insertions(+), 47 deletions(-) diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 6fd0876f..18c33e67 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -15,4 +15,4 @@ jobs: # one day this will be enabled # run: mypy --strict --module archinstall || exit 0 - name: run mypy - run: mypy --follow-imports=skip archinstall/lib/menu/selection_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py + run: mypy --follow-imports=silent archinstall/lib/menu/selection_menu.py archinstall/lib/menu/global_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.py diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index 995ca355..15f03789 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -34,11 +34,29 @@ class BlockDevice: def __repr__(self, *args :str, **kwargs :str) -> str: return self._str_repr - + @cached_property def _str_repr(self) -> str: return f"BlockDevice({self.device_or_backfile}, size={self._safe_size}GB, free_space={self._safe_free_space}, bus_type={self.bus_type})" + @cached_property + def display_info(self) -> str: + columns = { + str(_('Device')): self.device_or_backfile, + str(_('Size')): f'{self._safe_size}GB', + str(_('Free space')): f'{self._safe_free_space}', + str(_('Bus-type')): f'{self.bus_type}' + } + + padding = max([len(k) for k in columns.keys()]) + + pretty = '' + for k, v in columns.items(): + k = k.ljust(padding, ' ') + pretty += f'{k} = {v}\n' + + return pretty.rstrip() + def __iter__(self) -> Iterator[Partition]: for partition in self.partitions: yield self.partitions[partition] @@ -79,7 +97,7 @@ class BlockDevice: for device in output['blockdevices']: return device['pttype'] - @property + @cached_property def device_or_backfile(self) -> str: """ Returns the actual device-endpoint of the BlockDevice. @@ -162,7 +180,7 @@ class BlockDevice: from .filesystem import GPT return GPT - @property + @cached_property def uuid(self) -> str: log('BlockDevice().uuid is untested!', level=logging.WARNING, fg='yellow') """ @@ -172,7 +190,7 @@ class BlockDevice: """ return SysCommand(f'blkid -s PTUUID -o value {self.path}').decode('UTF-8') - @property + @cached_property def _safe_size(self) -> float: from .helpers import convert_size_to_gb @@ -184,7 +202,7 @@ class BlockDevice: for device in output['blockdevices']: return convert_size_to_gb(device['size']) - @property + @cached_property def size(self) -> float: from .helpers import convert_size_to_gb @@ -193,28 +211,28 @@ class BlockDevice: for device in output['blockdevices']: return convert_size_to_gb(device['size']) - @property + @cached_property def bus_type(self) -> str: output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) for device in output['blockdevices']: return device['tran'] - @property + @cached_property def spinning(self) -> bool: output = json.loads(SysCommand(f"lsblk --json -o+ROTA,TRAN {self.path}").decode('UTF-8')) for device in output['blockdevices']: return device['rota'] is True - @property + @cached_property def _safe_free_space(self) -> Tuple[str, ...]: try: return '+'.join(part[2] for part in self.free_space) except SysCallError: return '?' - @property + @cached_property def free_space(self) -> Tuple[str, ...]: # NOTE: parted -s will default to `cancel` on prompt, skipping any partition # that is "outside" the disk. in /dev/sr0 this is usually the case with Archiso, @@ -228,7 +246,7 @@ class BlockDevice: except SysCallError as error: log(f"Could not get free space on {self.path}: {error}", level=logging.DEBUG) - @property + @cached_property def largest_free_space(self) -> List[str]: info = [] for space_info in self.free_space: @@ -240,7 +258,7 @@ class BlockDevice: info = space_info return info - @property + @cached_property def first_free_sector(self) -> str: if info := self.largest_free_space: start = info[0] @@ -248,7 +266,7 @@ class BlockDevice: start = '512MB' return start - @property + @cached_property def first_end_sector(self) -> str: if info := self.largest_free_space: end = info[1] diff --git a/archinstall/lib/hsm/fido.py b/archinstall/lib/hsm/fido.py index 8707ac52..49f36957 100644 --- a/archinstall/lib/hsm/fido.py +++ b/archinstall/lib/hsm/fido.py @@ -40,7 +40,7 @@ def get_fido2_devices() -> typing.Dict[str, typing.Dict[str, str]]: } return devices - + def fido2_enroll(hsm_device_path :pathlib.Path, partition :Partition, password :str) -> bool: worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device_path} {partition.real_device}", peak_output=True) pw_inputted = False diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py index d807433c..53e0941d 100644 --- a/archinstall/lib/menu/global_menu.py +++ b/archinstall/lib/menu/global_menu.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, List, Optional, Union +from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING import archinstall @@ -33,10 +33,15 @@ from ..user_interaction import select_encrypted_partitions from ..user_interaction import select_harddrives from ..user_interaction import select_profile from ..user_interaction import select_additional_repositories +from ..user_interaction.partitioning_conf import current_partition_layout + +if TYPE_CHECKING: + _: Any + class GlobalMenu(GeneralMenu): def __init__(self,data_store): - super().__init__(data_store=data_store, auto_cursor=True) + super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3) def _setup_selection_menu_options(self): # archinstall.Language will not use preset values @@ -69,7 +74,10 @@ class GlobalMenu(GeneralMenu): self._menu_options['harddrives'] = \ Selector( _('Drive(s)'), - lambda preset: self._select_harddrives(preset)) + lambda preset: self._select_harddrives(preset), + display_func=lambda x: f'{len(x)} ' + str(_('Drive(s)')) if x is not None and len(x) > 0 else '', + preview_func=self._prev_harddrives, + ) self._menu_options['disk_layouts'] = \ Selector( _('Disk layout'), @@ -78,6 +86,8 @@ class GlobalMenu(GeneralMenu): storage['arguments'].get('harddrives', []), storage['arguments'].get('advanced', False) ), + preview_func=self._prev_disk_layouts, + display_func=lambda x: self._display_disk_layout(x), dependencies=['harddrives']) self._menu_options['!encryption-password'] = \ Selector( @@ -131,7 +141,8 @@ class GlobalMenu(GeneralMenu): Selector( _('Profile'), lambda preset: self._select_profile(preset), - display_func=lambda x: x if x else 'None') + display_func=lambda x: x if x else 'None' + ) self._menu_options['audio'] = \ Selector( _('Audio'), @@ -189,7 +200,7 @@ class GlobalMenu(GeneralMenu): def _update_install_text(self, name :str = None, result :Any = None): text = self._install_text() - self._menu_options.get('install').update_description(text) + self._menu_options['install'].update_description(text) def post_callback(self,name :str = None ,result :Any = None): self._update_install_text(name, result) @@ -225,6 +236,35 @@ class GlobalMenu(GeneralMenu): else: return str(cur_value) + def _prev_harddrives(self) -> Optional[str]: + selector = self._menu_options['harddrives'] + if selector.has_selection(): + drives = selector.current_selection + return '\n\n'.join([d.display_info for d in drives]) + return None + + def _prev_disk_layouts(self) -> Optional[str]: + selector = self._menu_options['disk_layouts'] + if selector.has_selection(): + layouts: Dict[str, Dict[str, Any]] = selector.current_selection + + output = '' + for device, layout in layouts.items(): + output += f'{_("Device")}: {device}\n\n' + output += current_partition_layout(layout['partitions'], with_title=False) + output += '\n\n' + + return output.rstrip() + + return None + + def _display_disk_layout(self, current_value: Optional[Dict[str, Any]]) -> str: + if current_value: + total_partitions = [entry['partitions'] for entry in current_value.values()] + total_nr = sum([len(p) for p in total_partitions]) + return f'{total_nr} {_("Partitions")}' + return '' + def _prev_install_missing_config(self) -> Optional[str]: if missing := self._missing_configs(): text = str(_('Missing configurations:\n')) @@ -247,17 +287,17 @@ class GlobalMenu(GeneralMenu): if not check('harddrives'): missing += ['Hard drives'] if check('harddrives'): - if not self._menu_options.get('harddrives').is_empty() and not check('disk_layouts'): + if not self._menu_options['harddrives'].is_empty() and not check('disk_layouts'): missing += ['Disk layout'] return missing - def _set_root_password(self): + def _set_root_password(self) -> Optional[str]: prompt = str(_('Enter root password (leave blank to disable root): ')) password = get_password(prompt=prompt) return password - def _select_encrypted_password(self): + def _select_encrypted_password(self) -> Optional[str]: if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))): return passwd else: @@ -271,7 +311,7 @@ class GlobalMenu(GeneralMenu): return ntp - def _select_harddrives(self, old_harddrives : list) -> list: + def _select_harddrives(self, old_harddrives : list) -> List: harddrives = select_harddrives(old_harddrives) if len(harddrives) == 0: @@ -288,7 +328,7 @@ class GlobalMenu(GeneralMenu): # in case the harddrives got changed we have to reset the disk layout as well if old_harddrives != harddrives: - self._menu_options.get('disk_layouts').set_current_selection(None) + self._menu_options['disk_layouts'].set_current_selection(None) storage['arguments']['disk_layouts'] = {} return harddrives @@ -340,11 +380,11 @@ class GlobalMenu(GeneralMenu): return ret - def _create_superuser_account(self): + def _create_superuser_account(self) -> Optional[Dict[str, Dict[str, str]]]: superusers = ask_for_superuser_account(str(_('Manage superuser accounts: '))) return superusers if superusers else None - def _create_user_account(self): + def _create_user_account(self) -> Dict[str, Dict[str, str | None]]: users = ask_for_additional_users(str(_('Manage ordinary user accounts: '))) return users @@ -356,7 +396,7 @@ class GlobalMenu(GeneralMenu): else: return list(superusers.keys()) if superusers else '' - def _users_resynch(self): + def _users_resynch(self) -> bool: self.synch('!superusers') self.synch('!users') return False diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 9faa1c77..7db3b3a9 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -89,7 +89,7 @@ from .text_input import TextInput from .menu import Menu, MenuSelectionType from os import system from copy import copy -from typing import Union, Any, TYPE_CHECKING, Dict +from typing import Union, Any, TYPE_CHECKING, Dict, Optional if TYPE_CHECKING: _: Any @@ -147,7 +147,7 @@ class ListManager: 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.target: Optional[Any] = None self.action = self._null_action if len(self._data) == 0 and self._null_action: diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py index 26be4cc7..57e290f1 100644 --- a/archinstall/lib/menu/selection_menu.py +++ b/archinstall/lib/menu/selection_menu.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: _: Any -def select_archinstall_language(preset_value: str) -> Optional[str]: +def select_archinstall_language(preset_value: str) -> Optional[Any]: """ copied from user_interaction/general_conf.py as a temporary measure """ @@ -487,6 +487,8 @@ class GeneralMenu: match choice.type_: case MenuSelectionType.Esc: return preset case MenuSelectionType.Selection: - return pathlib.Path(list(fido_devices.keys())[int(choice.value.split('|',1)[0])]) + selection: Any = choice.value + index = int(selection.split('|',1)[0]) + return pathlib.Path(list(fido_devices.keys())[index]) - return None \ No newline at end of file + return None diff --git a/archinstall/lib/user_interaction/general_conf.py b/archinstall/lib/user_interaction/general_conf.py index c3a2a7a7..d4dc60db 100644 --- a/archinstall/lib/user_interaction/general_conf.py +++ b/archinstall/lib/user_interaction/general_conf.py @@ -142,7 +142,6 @@ def select_profile(preset) -> Optional[Profile]: options[option] = profile title = _('This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments') - warning = str(_('Are you sure you want to reset this setting?')) selection = Menu( diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py index e4e681ce..25e9d4c6 100644 --- a/archinstall/lib/user_interaction/network_conf.py +++ b/archinstall/lib/user_interaction/network_conf.py @@ -64,7 +64,7 @@ class ManualNetworkConfig(ListManager): elif self.action == self._action_delete: del data[iface_name] - def _select_iface(self, existing_ifaces: List[str]) -> Optional[str]: + def _select_iface(self, existing_ifaces: List[str]) -> Optional[Any]: all_ifaces = list_interfaces().values() available = set(all_ifaces) - set(existing_ifaces) choice = Menu(str(_('Select interface to add')), list(available), skip=True).run() @@ -94,14 +94,14 @@ class ManualNetworkConfig(ListManager): log("You need to enter a valid IP in IP-config mode.", level=logging.WARNING, fg='red') # Implemented new check for correct gateway IP address + gateway = None + while 1: - gateway = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '), + gateway_input = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '), edit_iface.gateway).run().strip() try: - if len(gateway) == 0: - gateway = None - else: - ipaddress.ip_address(gateway) + if len(gateway_input) > 0: + ipaddress.ip_address(gateway_input) break except ValueError: log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red') diff --git a/archinstall/lib/user_interaction/partitioning_conf.py b/archinstall/lib/user_interaction/partitioning_conf.py index 741decc1..bfff5705 100644 --- a/archinstall/lib/user_interaction/partitioning_conf.py +++ b/archinstall/lib/user_interaction/partitioning_conf.py @@ -20,9 +20,9 @@ def partition_overlap(partitions: list, start: str, end: str) -> bool: return False -def _current_partition_layout(partitions: List[Partition], with_idx: bool = False) -> str: +def current_partition_layout(partitions: List[Dict[str, Any]], with_idx: bool = False, with_title: bool = True) -> str: - def do_padding(name, max_len): + def do_padding(name: str, max_len: int): spaces = abs(len(str(name)) - max_len) + 2 pad_left = int(spaces / 2) pad_right = spaces - pad_left @@ -62,8 +62,11 @@ def _current_partition_layout(partitions: List[Partition], with_idx: bool = Fals current_layout += f'{row[:-1]}\n' - title = str(_('Current partition layout')) - return f'\n\n{title}:\n\n{current_layout}' + if with_title: + title = str(_('Current partition layout')) + return f'\n\n{title}:\n\n{current_layout}' + + return current_layout def _get_partitions(partitions :List[Partition], filter_ :Callable = None) -> List[str]: @@ -173,7 +176,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, # show current partition layout: if len(block_device_struct["partitions"]): - title += _current_partition_layout(block_device_struct['partitions']) + '\n' + title += current_partition_layout(block_device_struct['partitions']) + '\n' modes += [save_and_exit, cancel] @@ -246,7 +249,7 @@ def manage_new_and_existing_partitions(block_device: 'BlockDevice') -> Dict[str, block_device_struct.update(suggest_single_disk_layout(block_device)[block_device.path]) else: - current_layout = _current_partition_layout(block_device_struct['partitions'], with_idx=True) + current_layout = current_partition_layout(block_device_struct['partitions'], with_idx=True) if task == delete_partition: title = _('{}\n\nSelect by index which partitions to delete').format(current_layout) @@ -375,7 +378,7 @@ def select_encrypted_partitions( # show current partition layout: if len(partitions): - title += _current_partition_layout(partitions) + '\n' + title += current_partition_layout(partitions) + '\n' choice = Menu(title, partition_indexes, multi=multiple).run() @@ -386,4 +389,4 @@ def select_encrypted_partitions( for partition_index in choice.value: yield int(partition_index) else: - yield (partition_index) \ No newline at end of file + yield (partition_index) diff --git a/archinstall/lib/user_interaction/utils.py b/archinstall/lib/user_interaction/utils.py index ce48607d..fa079bc2 100644 --- a/archinstall/lib/user_interaction/utils.py +++ b/archinstall/lib/user_interaction/utils.py @@ -52,6 +52,7 @@ def get_password(prompt: str = '') -> Optional[str]: continue return passwd + return None -- cgit v1.2.3-54-g00ecf