Specify menu return types using generics instead of Any (#3400)

This commit is contained in:
correctmost 2025-04-27 03:01:17 +00:00 committed by GitHub
parent fae210dfea
commit 0de90bd55b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 114 additions and 112 deletions

View File

@ -68,7 +68,7 @@ class DesktopProfile(Profile):
group = MenuItemGroup(items, sort_items=True, sort_case_sensitive=False)
group.set_selected_by_value(self.current_selection)
result = SelectMenu(
result = SelectMenu[Profile](
group,
multi=True,
allow_reset=True,

View File

@ -62,7 +62,7 @@ class HyprlandProfile(XorgProfile):
default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default)
result = SelectMenu(
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,

View File

@ -60,7 +60,7 @@ class LabwcProfile(XorgProfile):
default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default)
result = SelectMenu(
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,

View File

@ -68,7 +68,7 @@ class NiriProfile(XorgProfile):
default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default)
result = SelectMenu(
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,

View File

@ -70,7 +70,7 @@ class SwayProfile(XorgProfile):
default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default)
result = SelectMenu(
result = SelectMenu[SeatAccess](
group,
header=header,
allow_skip=False,

View File

@ -33,7 +33,7 @@ class ServerProfile(Profile):
group = MenuItemGroup(items, sort_items=True)
group.set_selected_by_value(self.current_selection)
result = SelectMenu(
result = SelectMenu[Profile](
group,
allow_reset=True,
allow_skip=True,

View File

@ -69,7 +69,7 @@ class ConfigurationOutput:
group.focus_item = MenuItem.yes()
group.set_preview_for_all(lambda x: self.user_config_to_json())
result = SelectMenu(
result = SelectMenu[bool](
group,
header=header,
alignment=Alignment.CENTER,
@ -168,7 +168,7 @@ def save_config(config: ArchConfig) -> None:
]
group = MenuItemGroup(items)
result = SelectMenu(
result = SelectMenu[str](
group,
allow_skip=True,
preview_frame=FrameProperties.max(str(_('Configuration'))),

View File

@ -22,7 +22,7 @@ class DiskMenuConfig:
lvm_config: LvmConfiguration | None
class DiskLayoutConfigurationMenu(AbstractSubMenu):
class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskLayoutConfiguration]):
def __init__(self, disk_layout_config: DiskLayoutConfiguration | None):
if not disk_layout_config:
self._disk_menu_config = DiskMenuConfig(disk_config=None, lvm_config=None)
@ -101,7 +101,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu):
if not item.value:
return None
disk_layout_conf: DiskLayoutConfiguration = item.get_value()
disk_layout_conf = item.get_value()
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
msg = str(_('Configuration type: {}')).format(disk_layout_conf.config_type.display_msg()) + '\n'

View File

@ -31,7 +31,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class DiskEncryptionMenu(AbstractSubMenu):
class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
def __init__(
self,
disk_config: DiskLayoutConfiguration,
@ -233,7 +233,7 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
group = MenuItemGroup(items)
group.set_focus_by_value(preset_value)
result = SelectMenu(
result = SelectMenu[EncryptionType](
group,
allow_skip=True,
allow_reset=True,
@ -273,7 +273,7 @@ def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
group, table_header = MenuHelper.create_table(data=fido_devices)
header = f'{header}\n\n{table_header}'
result = SelectMenu(
result = SelectMenu[Fido2Device](
group,
header=header,
alignment=Alignment.CENTER,
@ -309,7 +309,7 @@ def select_partitions_to_encrypt(
if avail_partitions:
group, header = MenuHelper.create_table(data=avail_partitions)
result = SelectMenu(
result = SelectMenu[PartitionModification](
group,
header=header,
alignment=Alignment.CENTER,
@ -337,7 +337,7 @@ def select_lvm_vols_to_encrypt(
if volumes:
group, header = MenuHelper.create_table(data=volumes)
result = SelectMenu(
result = SelectMenu[LvmVolume](
group,
header=header,
alignment=Alignment.CENTER,

View File

@ -77,7 +77,7 @@ class DiskSegment:
return data
class PartitioningList(ListManager):
class PartitioningList(ListManager[DiskSegment]):
def __init__(
self,
device_mod: DeviceModification,
@ -438,7 +438,7 @@ class PartitioningList(ListManager):
items = [MenuItem(fs.value, value=fs) for fs in fs_types]
group = MenuItemGroup(items, sort_items=False)
result = SelectMenu(
result = SelectMenu[FilesystemType](
group,
header=prompt,
alignment=Alignment.CENTER,
@ -509,7 +509,7 @@ class PartitioningList(ListManager):
title = str(_('Size (default: {}): ')).format(max_size.format_highest())
result = EditMenu(
result = EditMenu[str](
title,
header=f'{prompt}\b',
allow_skip=True,
@ -564,7 +564,7 @@ class PartitioningList(ListManager):
def _reset_confirmation(self) -> bool:
prompt = str(_('This will remove all newly added partitions, continue?')) + '\n'
result = SelectMenu(
result = SelectMenu[bool](
MenuItemGroup.yes_no(),
header=prompt,
alignment=Alignment.CENTER,

View File

@ -17,7 +17,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class SubvolumeMenu(ListManager):
class SubvolumeMenu(ListManager[SubvolumeModification]):
def __init__(
self,
btrfs_subvols: list[SubvolumeModification],
@ -41,7 +41,7 @@ class SubvolumeMenu(ListManager):
return str(selection.name)
def _add_subvolume(self, preset: SubvolumeModification | None = None) -> SubvolumeModification | None:
result = EditMenu(
result = EditMenu[str](
str(_('Subvolume name')),
alignment=Alignment.CENTER,
allow_skip=True,

View File

@ -43,7 +43,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class GlobalMenu(AbstractMenu):
class GlobalMenu(AbstractMenu[None]):
def __init__(self, arch_config: ArchConfig) -> None:
self._arch_config = arch_config
menu_optioons = self._get_menu_options()

View File

@ -46,7 +46,7 @@ if TYPE_CHECKING:
def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
def _preview_device_selection(item: MenuItem) -> str | None:
device: _DeviceInfo = item.get_value()
device = item.get_value()
dev = device_handler.get_device(device.path)
if dev and dev.partition_infos:
@ -64,7 +64,7 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
group.set_selected_by_value(presets)
group.set_preview_for_all(_preview_device_selection)
result = SelectMenu(
result = SelectMenu[_DeviceInfo](
group,
header=header,
alignment=Alignment.CENTER,
@ -82,7 +82,7 @@ def select_devices(preset: list[BDevice] | None = []) -> list[BDevice]:
case ResultType.Skip:
return preset
case ResultType.Selection:
selected_device_info: list[_DeviceInfo] = result.get_values()
selected_device_info = result.get_values()
selected_devices = []
for device in devices:
@ -140,7 +140,7 @@ def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLay
if preset:
group.set_selected_by_value(preset.config_type.display_msg())
result = SelectMenu(
result = SelectMenu[str](
group,
allow_skip=True,
alignment=Alignment.CENTER,
@ -210,7 +210,7 @@ def select_lvm_config(
group = MenuItemGroup(items)
group.set_focus_by_value(preset_value)
result = SelectMenu(
result = SelectMenu[str](
group,
allow_reset=True,
allow_skip=True,
@ -261,7 +261,7 @@ def select_main_filesystem_format() -> FilesystemType:
items.append(MenuItem('ntfs', value=FilesystemType.Ntfs))
group = MenuItemGroup(items, sort_items=False)
result = SelectMenu(
result = SelectMenu[FilesystemType](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min('Filesystem'),
@ -285,7 +285,7 @@ def select_mount_options() -> list[str]:
MenuItem(disable_cow, value=BtrfsMountOption.nodatacow.value),
]
group = MenuItemGroup(items, sort_items=False)
result = SelectMenu(
result = SelectMenu[str](
group,
header=prompt,
alignment=Alignment.CENTER,
@ -336,7 +336,7 @@ def suggest_single_disk_layout(
prompt = str(_('Would you like to use BTRFS subvolumes with a default structure?')) + '\n'
group = MenuItemGroup.yes_no()
group.set_focus_by_value(MenuItem.yes().value)
result = SelectMenu(
result = SelectMenu[bool](
group,
header=prompt,
alignment=Alignment.CENTER,
@ -577,7 +577,7 @@ def suggest_lvm_layout(
group = MenuItemGroup.yes_no()
group.set_focus_by_value(MenuItem.yes().value)
result = SelectMenu(
result = SelectMenu[bool](
group,
header=prompt,
search_enabled=False,

View File

@ -42,7 +42,7 @@ def ask_ntp(preset: bool = True) -> bool:
group = MenuItemGroup.yes_no()
group.focus_item = preset_val
result = SelectMenu(
result = SelectMenu[bool](
group,
header=header,
allow_skip=True,
@ -61,7 +61,7 @@ def ask_ntp(preset: bool = True) -> bool:
def ask_hostname(preset: str | None = None) -> str | None:
result = EditMenu(
result = EditMenu[str](
str(_('Hostname')),
alignment=Alignment.CENTER,
allow_skip=True,
@ -89,7 +89,7 @@ def ask_for_a_timezone(preset: str | None = None) -> str | None:
group.set_selected_by_value(preset)
group.set_default_by_value(default)
result = SelectMenu(
result = SelectMenu[str](
group,
allow_reset=True,
allow_skip=True,
@ -113,7 +113,7 @@ def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioCo
if preset:
group.set_focus_by_value(preset.audio)
result = SelectMenu(
result = SelectMenu[Audio](
group,
allow_skip=True,
alignment=Alignment.CENTER,
@ -156,7 +156,7 @@ def select_archinstall_language(languages: list[Language], preset: Language) ->
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
result = SelectMenu(
result = SelectMenu[Language](
group,
header=title,
allow_skip=True,
@ -215,7 +215,7 @@ def ask_additional_packages_to_install(
menu_group = MenuItemGroup(items, sort_items=True)
menu_group.set_selected_by_value(preset_packages)
result = SelectMenu(
result = SelectMenu[AvailablePackage | PackageGroup](
menu_group,
header=header,
alignment=Alignment.LEFT,
@ -233,7 +233,7 @@ def ask_additional_packages_to_install(
case ResultType.Reset:
return []
case ResultType.Selection:
selected_pacakges: list[AvailablePackage | PackageGroup] = result.get_values()
selected_pacakges = result.get_values()
return [pkg.name for pkg in selected_pacakges]
@ -255,7 +255,7 @@ def add_number_of_parallel_downloads(preset: int | None = None) -> int | None:
return str(_('Invalid download number'))
result = EditMenu(
result = EditMenu[str](
str(_('Number downloads')),
header=header,
allow_skip=True,
@ -295,7 +295,7 @@ def ask_post_installation() -> PostInstallationAction:
items = [MenuItem(action.value, value=action) for action in PostInstallationAction]
group = MenuItemGroup(items)
result = SelectMenu(
result = SelectMenu[PostInstallationAction](
group,
header=header,
allow_skip=False,
@ -313,7 +313,7 @@ def ask_abort() -> None:
prompt = str(_('Do you really want to abort?')) + '\n'
group = MenuItemGroup.yes_no()
result = SelectMenu(
result = SelectMenu[bool](
group,
header=prompt,
allow_skip=False,

View File

@ -20,7 +20,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class UserList(ListManager):
class UserList(ListManager[User]):
def __init__(self, prompt: str, lusers: list[User]):
self._actions = [
str(_('Add a user')),
@ -70,7 +70,7 @@ class UserList(ListManager):
return str(_("The username you entered is invalid"))
def _add_user(self) -> User | None:
editResult = EditMenu(
editResult = EditMenu[str](
str(_('Username')),
allow_skip=True,
validator=self._check_for_correct_username
@ -97,7 +97,7 @@ class UserList(ListManager):
group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.yes()
result = SelectMenu(
result = SelectMenu[bool](
group,
header=header,
alignment=Alignment.CENTER,

View File

@ -20,7 +20,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class ManualNetworkConfig(ListManager):
class ManualNetworkConfig(ListManager[Nic]):
def __init__(self, prompt: str, preset: list[Nic]):
self._actions = [
str(_('Add interface')),
@ -70,7 +70,7 @@ class ManualNetworkConfig(ListManager):
items = [MenuItem(i, value=i) for i in available]
group = MenuItemGroup(items, sort_items=True)
result = SelectMenu(
result = SelectMenu[str](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Interfaces'))),
@ -106,7 +106,7 @@ class ManualNetworkConfig(ListManager):
except ValueError:
return str(_('You need to enter a valid IP in IP-config mode'))
result = EditMenu(
result = EditMenu[str](
title,
header=header,
validator=validator,
@ -132,7 +132,7 @@ class ManualNetworkConfig(ListManager):
group = MenuItemGroup(items, sort_items=True)
group.set_default_by_value(default_mode)
result = SelectMenu(
result = SelectMenu[str](
group,
header=header,
allow_skip=False,
@ -192,7 +192,7 @@ def ask_to_configure_network(preset: NetworkConfiguration | None) -> NetworkConf
if preset:
group.set_selected_by_value(preset.type)
result = SelectMenu(
result = SelectMenu[NetworkConfiguration](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Network configuration'))),

View File

@ -35,7 +35,7 @@ def select_kernel(preset: list[str] = []) -> list[str]:
group.set_focus_by_value(default_kernel)
group.set_selected_by_value(preset)
result = SelectMenu(
result = SelectMenu[str](
group,
allow_skip=True,
allow_reset=True,
@ -69,7 +69,7 @@ def ask_for_bootloader(preset: Bootloader | None) -> Bootloader | None:
group.set_default_by_value(default)
group.set_focus_by_value(preset)
result = SelectMenu(
result = SelectMenu[Bootloader](
group,
header=header,
alignment=Alignment.CENTER,
@ -92,7 +92,7 @@ def ask_for_uki(preset: bool = True) -> bool:
group = MenuItemGroup.yes_no()
group.set_focus_by_value(preset)
result = SelectMenu(
result = SelectMenu[bool](
group,
header=prompt,
columns=2,
@ -136,7 +136,7 @@ def select_driver(options: list[GfxDriver] = [], preset: GfxDriver | None = None
if SysInfo.has_nvidia_graphics():
header += str(_('For the best compatibility with your Nvidia hardware, you may want to use the Nvidia proprietary driver.\n'))
result = SelectMenu(
result = SelectMenu[GfxDriver](
group,
header=header,
allow_skip=True,
@ -166,7 +166,7 @@ def ask_for_swap(preset: bool = True) -> bool:
group = MenuItemGroup.yes_no()
group.set_focus_by_value(default_item)
result = SelectMenu(
result = SelectMenu[bool](
group,
header=prompt,
columns=2,

View File

@ -17,7 +17,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class LocaleMenu(AbstractSubMenu):
class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
def __init__(
self,
locale_conf: LocaleConfiguration
@ -85,7 +85,7 @@ def select_locale_lang(preset: str | None = None) -> str | None:
group = MenuItemGroup(items, sort_items=True)
group.set_focus_by_value(preset)
result = SelectMenu(
result = SelectMenu[str](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Locale language'))),
@ -109,7 +109,7 @@ def select_locale_enc(preset: str | None = None) -> str | None:
group = MenuItemGroup(items, sort_items=True)
group.set_focus_by_value(preset)
result = SelectMenu(
result = SelectMenu[str](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Locale encoding'))),
@ -141,7 +141,7 @@ def select_kb_layout(preset: str | None = None) -> str | None:
group = MenuItemGroup(items, sort_items=False)
group.set_focus_by_value(preset)
result = SelectMenu(
result = SelectMenu[str](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Keyboard layout'))),

View File

@ -19,7 +19,7 @@ if TYPE_CHECKING:
CONFIG_KEY = '__config__'
class AbstractMenu:
class AbstractMenu[ValueT]:
def __init__(
self,
item_group: MenuItemGroup,
@ -97,11 +97,11 @@ class AbstractMenu:
def _is_config_valid(self) -> bool:
return True
def run(self) -> Any | None:
def run(self) -> ValueT | None:
self._sync_from_config()
while True:
result = SelectMenu(
result = SelectMenu[ValueT](
self._menu_item_group,
allow_skip=False,
allow_reset=self._allow_reset,
@ -126,7 +126,7 @@ class AbstractMenu:
return None
class AbstractSubMenu(AbstractMenu):
class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
def __init__(
self,
item_group: MenuItemGroup,

View File

@ -1,5 +1,5 @@
import copy
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast
from archinstall.tui.curses_menu import SelectMenu
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
@ -16,10 +16,10 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class ListManager:
class ListManager[ValueT]:
def __init__(
self,
entries: list[Any],
entries: list[ValueT],
base_actions: list[str],
sub_menu_actions: list[str],
prompt: str | None = None
@ -51,10 +51,10 @@ class ListManager:
self._base_actions = base_actions
self._sub_menu_actions = sub_menu_actions
self._last_choice: str | None = None
self._last_choice: ValueT | str | None = None
@property
def last_choice(self) -> str | None:
def last_choice(self) -> ValueT | str | None:
return self._last_choice
def is_last_choice_cancel(self) -> bool:
@ -62,7 +62,7 @@ class ListManager:
return self._last_choice == self._cancel_action
return False
def run(self) -> list[Any]:
def run(self) -> list[ValueT]:
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
@ -77,7 +77,7 @@ class ListManager:
items = [MenuItem(o[0], value=o[1]) for o in options]
group = MenuItemGroup(items, sort_items=False)
result = SelectMenu(
result = SelectMenu[ValueT | str](
group,
header=header,
search_enabled=False,
@ -92,11 +92,14 @@ class ListManager:
raise ValueError('Unhandled return type')
if value in self._base_actions:
value = cast(str, value)
self._data = self.handle_action(value, None, self._data)
elif value in self._terminate_actions:
break
else: # an entry of the existing selection was chosen
selected_entry = result.get_value()
selected_entry = cast(ValueT, selected_entry)
self._run_actions_on_entry(selected_entry)
self._last_choice = value
@ -111,7 +114,7 @@ class ListManager:
header = '\n'.join(table_header)
return header
def _prepare_selection(self, data_formatted: dict[str, Any]) -> list[tuple[str, Any]]:
def _prepare_selection(self, data_formatted: dict[str, Any]) -> list[tuple[str, str | ValueT]]:
# header rows are mapped to None so make sure
# to exclude those from the selectable data
options = [(key, val) for key, val in data_formatted.items() if val is not None]
@ -125,7 +128,7 @@ class ListManager:
return options
def _run_actions_on_entry(self, entry: Any) -> None:
def _run_actions_on_entry(self, entry: ValueT) -> None:
options = self.filter_options(entry, self._sub_menu_actions) + [self._cancel_action]
items = [MenuItem(o, value=o) for o in options]
@ -133,7 +136,7 @@ class ListManager:
header = f'{self.selected_action_display(entry)}\n'
result = SelectMenu(
result = SelectMenu[str](
group,
header=header,
search_enabled=False,
@ -171,21 +174,21 @@ class ListManager:
return display_data
def selected_action_display(self, selection: Any) -> str:
def selected_action_display(self, selection: ValueT) -> 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: Any | None, data: list[Any]) -> list[Any]:
def handle_action(self, action: str, entry: ValueT | None, data: list[ValueT]) -> list[ValueT]:
"""
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]:
def filter_options(self, selection: ValueT, options: list[str]) -> list[str]:
"""
filter which actions to show for an specific selection
"""

View File

@ -32,7 +32,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class CustomMirrorRepositoriesList(ListManager):
class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
def __init__(self, custom_repositories: list[CustomRepository]):
self._actions = [
str(_('Add a custom repository')),
@ -74,7 +74,7 @@ class CustomMirrorRepositoriesList(ListManager):
return data
def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None:
edit_result = EditMenu(
edit_result = EditMenu[str](
str(_('Repository name')),
alignment=Alignment.CENTER,
allow_skip=True,
@ -91,7 +91,7 @@ class CustomMirrorRepositoriesList(ListManager):
header = f'{_("Name")}: {name}'
edit_result = EditMenu(
edit_result = EditMenu[str](
str(_('Url')),
header=header,
alignment=Alignment.CENTER,
@ -116,7 +116,7 @@ class CustomMirrorRepositoriesList(ListManager):
if preset is not None:
group.set_selected_by_value(preset.sign_check.value)
result = SelectMenu(
result = SelectMenu[SignCheck](
group,
header=prompt,
alignment=Alignment.CENTER,
@ -154,7 +154,7 @@ class CustomMirrorRepositoriesList(ListManager):
return CustomRepository(name, url, sign_check, sign_opt)
class CustomMirrorServersList(ListManager):
class CustomMirrorServersList(ListManager[CustomServer]):
def __init__(self, custom_servers: list[CustomServer]):
self._actions = [
str(_('Add a custom server')),
@ -196,7 +196,7 @@ class CustomMirrorServersList(ListManager):
return data
def _add_custom_server(self, preset: CustomServer | None = None) -> CustomServer | None:
edit_result = EditMenu(
edit_result = EditMenu[str](
str(_('Server url')),
alignment=Alignment.CENTER,
allow_skip=True,
@ -213,7 +213,7 @@ class CustomMirrorServersList(ListManager):
return None
class MirrorMenu(AbstractSubMenu):
class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
def __init__(
self,
preset: MirrorConfiguration | None = None
@ -265,7 +265,7 @@ class MirrorMenu(AbstractSubMenu):
]
def _prev_regions(self, item: MenuItem) -> str | None:
regions: list[MirrorRegion] = item.get_value()
regions = item.get_value()
output = ''
for region in regions:
@ -323,7 +323,7 @@ def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
group.set_selected_by_value(preset_regions)
result = SelectMenu(
result = SelectMenu[MirrorRegion](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Mirror regions'))),
@ -338,7 +338,7 @@ def select_mirror_regions(preset: list[MirrorRegion]) -> list[MirrorRegion]:
case ResultType.Reset:
return []
case ResultType.Selection:
selected_mirrors: list[MirrorRegion] = result.get_values()
selected_mirrors = result.get_values()
return selected_mirrors
@ -365,7 +365,7 @@ def select_optional_repositories(preset: list[Repository]) -> list[Repository]:
group = MenuItemGroup(items, sort_items=True)
group.set_selected_by_value(preset)
result = SelectMenu(
result = SelectMenu[Repository](
group,
alignment=Alignment.CENTER,
frame=FrameProperties.min('Additional repositories'),

View File

@ -21,7 +21,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class ProfileMenu(AbstractSubMenu):
class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
def __init__(
self,
preset: ProfileConfiguration | None = None
@ -114,7 +114,7 @@ class ProfileMenu(AbstractSubMenu):
group.focus_item = MenuItem.no()
group.default_item = MenuItem.no()
result = SelectMenu(
result = SelectMenu[bool](
group,
header=header,
allow_skip=False,
@ -175,7 +175,7 @@ def select_greeter(
group.set_default_by_value(default)
result = SelectMenu(
result = SelectMenu[GreeterType](
group,
allow_skip=True,
frame=FrameProperties.min(str(_('Greeter'))),
@ -208,7 +208,7 @@ def select_profile(
group = MenuItemGroup(items, sort_items=True)
group.set_selected_by_value(current_profile)
result = SelectMenu(
result = SelectMenu[Profile](
group,
header=header,
allow_reset=allow_reset,
@ -223,7 +223,7 @@ def select_profile(
case ResultType.Skip:
return current_profile
case ResultType.Selection:
profile_selection: Profile = result.get_value()
profile_selection = result.get_value()
select_result = profile_selection.do_on_select()
if not select_result:

View File

@ -31,7 +31,7 @@ def get_password(
elif header is not None:
user_hdr = header
result = EditMenu(
result = EditMenu[str](
text,
header=user_hdr,
alignment=Alignment.CENTER,
@ -53,7 +53,7 @@ def get_password(
else:
confirmation_header = f'{_("Password")}: {password.hidden()}\n'
result = EditMenu(
result = EditMenu[str](
str(_('Confirm password')),
header=confirmation_header,
alignment=Alignment.CENTER,
@ -87,7 +87,7 @@ def prompt_dir(
else:
validate_func = None
result = EditMenu(
result = EditMenu[str](
text,
header=header,
alignment=Alignment.CENTER,

View File

@ -35,7 +35,7 @@ if TYPE_CHECKING:
_: Callable[[str], DeferredTranslation]
class AbstractCurses(metaclass=ABCMeta):
class AbstractCurses[ValueT](metaclass=ABCMeta):
def __init__(self) -> None:
self._help_window = self._set_help_viewport()
@ -44,7 +44,7 @@ class AbstractCurses(metaclass=ABCMeta):
pass
@abstractmethod
def kickoff(self, win: curses.window) -> Result:
def kickoff(self, win: curses.window) -> Result[ValueT]:
pass
def clear_all(self) -> None:
@ -71,7 +71,7 @@ class AbstractCurses(metaclass=ABCMeta):
def _confirm_interrupt(self, warning: str) -> bool:
while True:
result = SelectMenu(
result = SelectMenu[bool](
MenuItemGroup.yes_no(),
header=warning,
alignment=Alignment.CENTER,
@ -461,7 +461,7 @@ class Viewport(AbstractViewport):
self._main_win.refresh()
class EditMenu(AbstractCurses):
class EditMenu[ValueT](AbstractCurses[ValueT]):
def __init__(
self,
title: str,
@ -506,7 +506,7 @@ class EditMenu(AbstractCurses):
self._init_viewports()
self._last_state: Result | None = None
self._last_state: Result[ValueT] | None = None
self._help_active = False
self._real_input = default_text or ""
@ -536,7 +536,7 @@ class EditMenu(AbstractCurses):
y_offset += 3
self._info_vp = Viewport(self._max_width, 1, 0, y_offset, alignment=self._alignment)
def input(self) -> Result:
def input(self) -> Result[ValueT]:
result = Tui.run(self)
assert not result.has_item() or isinstance(result.text(), str)
@ -593,7 +593,7 @@ class EditMenu(AbstractCurses):
self._input_vp.edit(default_text=self._default_text)
@override
def kickoff(self, win: curses.window) -> Result:
def kickoff(self, win: curses.window) -> Result[ValueT]:
try:
self._draw()
except KeyboardInterrupt:
@ -680,7 +680,7 @@ class EditMenu(AbstractCurses):
return True
class SelectMenu(AbstractCurses):
class SelectMenu[ValueT](AbstractCurses[ValueT]):
def __init__(
self,
group: MenuItemGroup,
@ -769,13 +769,13 @@ class SelectMenu(AbstractCurses):
return offset
def run(self) -> Result:
def run(self) -> Result[ValueT]:
result = Tui.run(self)
self._clear_all()
return result
@override
def kickoff(self, win: curses.window) -> Result:
def kickoff(self, win: curses.window) -> Result[ValueT]:
self._draw()
while True:
@ -1137,7 +1137,7 @@ class SelectMenu(AbstractCurses):
else:
return False
def _process_input_key(self, key: int) -> Result | None:
def _process_input_key(self, key: int) -> Result[ValueT] | None:
key_handles = MenuKeys.from_ord(key)
if self._help_active:
@ -1339,7 +1339,7 @@ class Tui:
return self._screen.getmaxyx()
@staticmethod
def run(component: AbstractCurses) -> Result:
def run[ValueT](component: AbstractCurses[ValueT]) -> Result[ValueT]:
if Tui._t is None:
tui = Tui().init()
tui.screen.clear()
@ -1355,7 +1355,7 @@ class Tui:
if hasattr(self, '_component') and self._component is not None: # pylint: disable=no-member
self._component.resize_win() # pylint: disable=no-member
def _main_loop(self, component: AbstractCurses) -> Result:
def _main_loop[ValueT](self, component: AbstractCurses[ValueT]) -> Result[ValueT]:
self._screen.refresh()
return component.kickoff(self._screen)

View File

@ -1,6 +1,5 @@
from dataclasses import dataclass
from enum import Enum, auto
from typing import Any
from .menu_item import MenuItem
@ -12,17 +11,17 @@ class ResultType(Enum):
@dataclass
class Result:
class Result[ValueT]:
type_: ResultType
_item: MenuItem | list[MenuItem] | str | None
def has_item(self) -> bool:
return self._item is not None
def get_value(self) -> Any:
return self.item().get_value()
def get_value(self) -> ValueT:
return self.item().get_value() # type: ignore[no-any-return]
def get_values(self) -> list[Any]:
def get_values(self) -> list[ValueT]:
return [i.get_value() for i in self.items()]
def item(self) -> MenuItem: