From b470b16ec923260cfd9c5b9f2b88e0a39611b463 Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Mon, 15 Apr 2024 18:49:00 +1000 Subject: LVM support (#2104) * Submenu for disk configuration * Update * Add LVM manual config * PV selection * LVM volume menu * Update * Fix mypy * Update * Update * Update * Update * Update * Update * Update * Update * Update LVM * Update * Update * Btrfs support * Refactor * LVM on Luks * Luks on LVM * Update * LVM on Luks * Update * Update * mypy * Update * Fix bug with LuksOnLvm and Btrfs * Update * Update * Info -> Debug output --- archinstall/lib/menu/abstract_menu.py | 93 ++++++++++++---------------- archinstall/lib/menu/list_manager.py | 28 ++++++--- archinstall/lib/menu/menu.py | 8 ++- archinstall/lib/menu/table_selection_menu.py | 4 +- 4 files changed, 68 insertions(+), 65 deletions(-) (limited to 'archinstall/lib/menu') diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py index 14db98ca..ee55f5c9 100644 --- a/archinstall/lib/menu/abstract_menu.py +++ b/archinstall/lib/menu/abstract_menu.py @@ -10,6 +10,7 @@ from ..translationhandler import TranslationHandler, Language if TYPE_CHECKING: _: Any + class Selector: def __init__( self, @@ -68,42 +69,19 @@ class Selector: :param no_store: A boolean which determines that the field should or shouldn't be stored in the data storage :type no_store: bool """ - self._description = description - self.func = func self._display_func = display_func - self._current_selection = default + self._no_store = no_store + + self.description = description + self.func = func + self.current_selection = default self.enabled = enabled - self._dependencies = dependencies - self._dependencies_not = dependencies_not + self.dependencies = dependencies + self.dependencies_not = dependencies_not self.exec_func = exec_func - self._preview_func = preview_func + self.preview_func = preview_func self.mandatory = mandatory - self._no_store = no_store - self._default = default - - @property - def default(self) -> Any: - return self._default - - @property - def description(self) -> str: - return self._description - - @property - def dependencies(self) -> List: - return self._dependencies - - @property - def dependencies_not(self) -> List: - return self._dependencies_not - - @property - def current_selection(self) -> Optional[Any]: - return self._current_selection - - @property - def preview_func(self): - return self._preview_func + self.default = default def do_store(self) -> bool: return self._no_store is False @@ -112,45 +90,45 @@ class Selector: self.enabled = status def update_description(self, description: str): - self._description = description + self.description = description def menu_text(self, padding: int = 0) -> str: - if self._description == '': # special menu option for __separator__ + if self.description == '': # special menu option for __separator__ return '' current = '' if self._display_func: - current = self._display_func(self._current_selection) + current = self._display_func(self.current_selection) else: - if self._current_selection is not None: - current = str(self._current_selection) + if self.current_selection is not None: + current = str(self.current_selection) if current: padding += 5 - description = unicode_ljust(str(self._description), padding, ' ') + description = unicode_ljust(str(self.description), padding, ' ') current = current else: - description = self._description + description = self.description current = '' return f'{description} {current}' def set_current_selection(self, current: Optional[Any]): - self._current_selection = current + self.current_selection = current def has_selection(self) -> bool: - if not self._current_selection: + if not self.current_selection: return False return True def get_selection(self) -> Any: - return self._current_selection + return self.current_selection def is_empty(self) -> bool: - if self._current_selection is None: + if self.current_selection is None: return True - elif isinstance(self._current_selection, (str, list, dict)) and len(self._current_selection) == 0: + elif isinstance(self.current_selection, (str, list, dict)) and len(self.current_selection) == 0: return True return False @@ -197,6 +175,8 @@ class AbstractMenu: self._sync_all() self._populate_default_values() + self.defined_text = str(_('Defined')) + @property def last_choice(self): return self._last_choice @@ -382,9 +362,10 @@ class AbstractMenu: result = None if selector.func is not None: - presel_val = self.option(config_name).get_selection() - result = selector.func(presel_val) + cur_value = self.option(config_name).get_selection() + result = selector.func(cur_value) self._menu_options[config_name].set_current_selection(result) + if selector.do_store(): self._data_store[config_name] = result @@ -398,19 +379,23 @@ class AbstractMenu: return True def _verify_selection_enabled(self, selection_name: str) -> bool: - """ general """ if selection := self._menu_options.get(selection_name, None): if not selection.enabled: return False if len(selection.dependencies) > 0: - for d in selection.dependencies: - if not self._verify_selection_enabled(d) or self._menu_options[d].is_empty(): - return False + for dep in selection.dependencies: + if isinstance(dep, str): + if not self._verify_selection_enabled(dep) or self._menu_options[dep].is_empty(): + return False + elif callable(dep): # callable dependency eval + return dep() + else: + raise ValueError(f'Unsupported dependency: {selection_name}') if len(selection.dependencies_not) > 0: - for d in selection.dependencies_not: - if not self._menu_options[d].is_empty(): + for dep in selection.dependencies_not: + if not self._menu_options[dep].is_empty(): return False return True @@ -454,8 +439,8 @@ class AbstractMenu: class AbstractSubMenu(AbstractMenu): - def __init__(self, data_store: Dict[str, Any] = {}): - super().__init__(data_store=data_store) + def __init__(self, data_store: Dict[str, Any] = {}, preview_size: float = 0.2): + super().__init__(data_store=data_store, preview_size=preview_size) self._menu_options['__separator__'] = Selector('') self._menu_options['back'] = \ diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py index 54fb6a1b..de18791c 100644 --- a/archinstall/lib/menu/list_manager.py +++ b/archinstall/lib/menu/list_manager.py @@ -3,6 +3,7 @@ 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 @@ -127,18 +128,29 @@ class ListManager: if choice.value and choice.value != self._cancel_action: self._data = self.handle_action(choice.value, entry, self._data) - def selected_action_display(self, selection: Any) -> str: + def reformat(self, data: List[Any]) -> Dict[str, Optional[Any]]: """ - this will return the value to be displayed in the - "Select an action for '{}'" string + Default implementation of the table to be displayed. + Override if any custom formatting is needed """ - raise NotImplementedError('Please implement me in the child class') + table = FormattedOutput.as_table(data) + rows = table.split('\n') - def reformat(self, data: List[Any]) -> Dict[str, Optional[Any]]: + # 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 should return a dictionary of display string to actual data entry - mapping; if the value for a given display string is None it will be used - in the header value (useful when displaying tables) + this will return the value to be displayed in the + "Select an action for '{}'" string """ raise NotImplementedError('Please implement me in the child class') diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py index f14b855d..38301d3a 100644 --- a/archinstall/lib/menu/menu.py +++ b/archinstall/lib/menu/menu.py @@ -66,7 +66,7 @@ class Menu(TerminalMenu): sort: bool = True, preset_values: Optional[Union[str, List[str]]] = None, cursor_index: Optional[int] = None, - preview_command: Optional[Callable] = None, + preview_command: Optional[Callable[[Any], str | None]] = None, preview_size: float = 0.0, preview_title: str = 'Info', header: Union[List[str], str] = [], @@ -228,7 +228,11 @@ class Menu(TerminalMenu): default_str = str(_('(default)')) return f'{self._default_option} {default_str}' - def _show_preview(self, preview_command: Optional[Callable], selection: str) -> Optional[str]: + def _show_preview( + self, + preview_command: Optional[Callable[[Any], str | None]], + selection: str + ) -> Optional[str]: if selection == self.back(): return None diff --git a/archinstall/lib/menu/table_selection_menu.py b/archinstall/lib/menu/table_selection_menu.py index 4cff7216..fec6ae59 100644 --- a/archinstall/lib/menu/table_selection_menu.py +++ b/archinstall/lib/menu/table_selection_menu.py @@ -19,6 +19,7 @@ class TableMenu(Menu): preview_size: float = 0.0, allow_reset: bool = True, allow_reset_warning_msg: Optional[str] = None, + skip: bool = True ): """ param title: Text that will be displayed above the menu @@ -81,7 +82,8 @@ class TableMenu(Menu): preview_title=preview_title, extra_bottom_space=extra_bottom_space, allow_reset=allow_reset, - allow_reset_warning_msg=allow_reset_warning_msg + allow_reset_warning_msg=allow_reset_warning_msg, + skip=skip ) def _preset_values(self, preset: List[Any]) -> List[str]: -- cgit v1.2.3-54-g00ecf