Enhance config types and summary (#4532)

* Enhance config types and summary

* Update
This commit is contained in:
Daniel Girtler 2026-05-11 06:35:53 +10:00 committed by GitHub
parent 2de7254b21
commit 7fc33c2507
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 411 additions and 115 deletions

View File

@ -6,6 +6,7 @@ import urllib.error
import urllib.parse
from argparse import ArgumentParser, Namespace
from dataclasses import dataclass, field
from enum import StrEnum
from pathlib import Path
from typing import Any, Self
from urllib.request import Request, urlopen
@ -17,6 +18,7 @@ from archinstall.lib.menu.util import get_password
from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration
from archinstall.lib.models.authentication import AuthenticationConfiguration
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.config import SubConfig
from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguration
from archinstall.lib.models.locale import LocaleConfiguration
from archinstall.lib.models.mirrors import MirrorConfiguration
@ -57,6 +59,81 @@ class Arguments:
verbose: bool = False
class ArchConfigType(StrEnum):
VERSION = 'version'
SCRIPT = 'script'
LOCALE_CONFIG = 'locale_config'
ARCHINSTALL_LANGUAGE = 'archinstall_language'
DISK_CONFIG = 'disk_config'
PROFILE_CONFIG = 'profile_config'
MIRROR_CONFIG = 'mirror_config'
NETWORK_CONFIG = 'network_config'
BOOTLOADER_CONFIG = 'bootloader_config'
APP_CONFIG = 'app_config'
AUTH_CONFIG = 'auth_config'
SWAP = 'swap'
USERS = 'users'
ROOT_ENC_PASSWORD = 'root_enc_password'
ENCRYPTION_PASSWORD = 'encryption_password'
HOSTNAME = 'hostname'
KERNELS = 'kernels'
NTP = 'ntp'
TIMEZONE = 'timezone'
SERVICES = 'services'
PACKAGES = 'packages'
PACMAN_CONFIG = 'pacman_config'
CUSTOM_COMMANDS = 'custom_commands'
def text(self) -> str:
match self:
case ArchConfigType.ARCHINSTALL_LANGUAGE:
return tr('ArchInstall Language')
case ArchConfigType.VERSION:
return tr('Version')
case ArchConfigType.SCRIPT:
return tr('Installation Script')
case ArchConfigType.LOCALE_CONFIG:
return tr('Locales')
case ArchConfigType.DISK_CONFIG:
return tr('Disk configuration')
case ArchConfigType.PROFILE_CONFIG:
return tr('Profile')
case ArchConfigType.MIRROR_CONFIG:
return tr('Mirrors and repositories')
case ArchConfigType.NETWORK_CONFIG:
return tr('Network')
case ArchConfigType.BOOTLOADER_CONFIG:
return tr('Bootloader')
case ArchConfigType.APP_CONFIG:
return tr('Application')
case ArchConfigType.AUTH_CONFIG:
return tr('Authentication')
case ArchConfigType.SWAP:
return tr('Swap')
case ArchConfigType.HOSTNAME:
return tr('Hostname')
case ArchConfigType.KERNELS:
return tr('Kernels')
case ArchConfigType.NTP:
return tr('Automatic time sync (NTP)')
case ArchConfigType.TIMEZONE:
return tr('Timezone')
case ArchConfigType.SERVICES:
return tr('Services')
case ArchConfigType.PACKAGES:
return tr('Additional packages')
case ArchConfigType.PACMAN_CONFIG:
return tr('Pacman')
case ArchConfigType.CUSTOM_COMMANDS:
return tr('Custom commands')
case ArchConfigType.USERS:
return tr('Users')
case ArchConfigType.ROOT_ENC_PASSWORD:
return tr('Root encrypted password')
case ArchConfigType.ENCRYPTION_PASSWORD:
return tr('Disk encryption password')
@dataclass
class ArchConfig:
version: str | None = None
@ -80,58 +157,84 @@ class ArchConfig:
services: list[str] = field(default_factory=list)
custom_commands: list[str] = field(default_factory=list)
def unsafe_config(self) -> dict[str, Any]:
config: dict[str, list[UserSerialization] | str | None] = {}
def unsafe_config(self) -> dict[ArchConfigType, Any]:
config: dict[ArchConfigType, list[UserSerialization] | str | None] = {}
if self.auth_config:
if self.auth_config.users:
config['users'] = [user.json() for user in self.auth_config.users]
config[ArchConfigType.USERS] = [user.json() for user in self.auth_config.users]
if self.auth_config.root_enc_password:
config['root_enc_password'] = self.auth_config.root_enc_password.enc_password
config[ArchConfigType.ROOT_ENC_PASSWORD] = self.auth_config.root_enc_password.enc_password
if self.disk_config:
disk_encryption = self.disk_config.disk_encryption
if disk_encryption and disk_encryption.encryption_password:
config['encryption_password'] = disk_encryption.encryption_password.plaintext
config[ArchConfigType.ENCRYPTION_PASSWORD] = disk_encryption.encryption_password.plaintext
return config
def safe_config(self) -> dict[str, Any]:
config: Any = {
'version': self.version,
'script': self.script,
'archinstall-language': self.archinstall_language.json(),
'hostname': self.hostname,
'kernels': self.kernels,
'ntp': self.ntp,
'packages': self.packages,
'pacman_config': self.pacman_config.json(),
'swap': self.swap,
'timezone': self.timezone,
'services': self.services,
'custom_commands': self.custom_commands,
'bootloader_config': self.bootloader_config.json() if self.bootloader_config else None,
'app_config': self.app_config.json() if self.app_config else None,
'auth_config': self.auth_config.json() if self.auth_config else None,
def safe_config(self) -> dict[ArchConfigType, Any]:
base_config: dict[ArchConfigType, Any] = {
ArchConfigType.VERSION: self.version,
ArchConfigType.SCRIPT: self.script,
ArchConfigType.ARCHINSTALL_LANGUAGE: self.archinstall_language.json(),
}
if self.locale_config:
config['locale_config'] = self.locale_config.json()
base_config.update(self.plain_cfg())
sub_config = self.sub_cfg()
if self.disk_config:
config['disk_config'] = self.disk_config.json()
for config_type, value in sub_config.items():
if not hasattr(value, 'json'):
raise ValueError(f'Config value for {config_type} must implement json() method')
base_config[config_type] = value.json()
if self.profile_config:
config['profile_config'] = self.profile_config.json()
return base_config
def plain_cfg(self) -> dict[ArchConfigType, str | list[str] | bool]:
return {
ArchConfigType.HOSTNAME: self.hostname,
ArchConfigType.KERNELS: self.kernels,
ArchConfigType.NTP: self.ntp,
ArchConfigType.TIMEZONE: self.timezone,
ArchConfigType.SERVICES: self.services,
ArchConfigType.PACKAGES: self.packages,
ArchConfigType.CUSTOM_COMMANDS: self.custom_commands,
}
def sub_cfg(self) -> dict[ArchConfigType, SubConfig]:
cfg: dict[ArchConfigType, SubConfig] = {
ArchConfigType.PACMAN_CONFIG: self.pacman_config,
}
if self.mirror_config:
config['mirror_config'] = self.mirror_config.json()
cfg[ArchConfigType.MIRROR_CONFIG] = self.mirror_config
if self.bootloader_config:
cfg[ArchConfigType.BOOTLOADER_CONFIG] = self.bootloader_config
if self.disk_config:
cfg[ArchConfigType.DISK_CONFIG] = self.disk_config
if self.swap:
cfg[ArchConfigType.SWAP] = self.swap
if self.auth_config:
cfg[ArchConfigType.AUTH_CONFIG] = self.auth_config
if self.locale_config:
cfg[ArchConfigType.LOCALE_CONFIG] = self.locale_config
if self.profile_config:
cfg[ArchConfigType.PROFILE_CONFIG] = self.profile_config
if self.network_config:
config['network_config'] = self.network_config.json()
cfg[ArchConfigType.NETWORK_CONFIG] = self.network_config
return config
if self.app_config:
cfg[ArchConfigType.APP_CONFIG] = self.app_config
return cfg
@classmethod
def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self:

View File

@ -6,13 +6,12 @@ from typing import Any
from pydantic import TypeAdapter
from archinstall.lib.args import ArchConfig
from archinstall.lib.args import ArchConfig, ArchConfigType
from archinstall.lib.crypt import encrypt
from archinstall.lib.menu.helpers import Confirmation, Selection
from archinstall.lib.menu.util import get_password, prompt_dir
from archinstall.lib.models.bootloader import Bootloader
from archinstall.lib.models.network import NetworkConfiguration
from archinstall.lib.output import debug, logger, warn
from archinstall.lib.output import as_key_value_pair, debug, logger, warn
from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
@ -45,15 +44,15 @@ class ConfigurationOutput:
def user_config_to_json(self) -> str:
config = self._config.safe_config()
adapter = TypeAdapter(dict[str, Any])
adapter = TypeAdapter(dict[ArchConfigType, Any])
python_dict = adapter.dump_python(config)
return json.dumps(python_dict, indent=4, sort_keys=True)
def user_credentials_to_json(self) -> str:
config = self._config.unsafe_config()
cfg = self._config.unsafe_config()
adapter = TypeAdapter(dict[str, Any])
python_dict = adapter.dump_python(config)
adapter = TypeAdapter(dict[ArchConfigType, Any])
python_dict = adapter.dump_python(cfg)
return json.dumps(python_dict, indent=4, sort_keys=True)
def write_debug(self) -> None:
@ -64,65 +63,24 @@ class ConfigurationOutput:
"""
Render a concise two-column summary of the current configuration.
The left column holds section labels, the right column holds values.
Column width adapts to the longest translated label so translations
do not break the alignment. Rows whose underlying config is not set
are skipped.
Returns an empty string if nothing meaningful to show.
"""
rows: list[tuple[str, str]] = []
cfg: dict[str, str | list[str] | bool] = {}
disk_config = self._config.disk_config
if disk_config and disk_config.device_modifications:
disk_parts: list[str] = []
for mod in disk_config.device_modifications:
path = str(mod.device_path)
root_part = mod.get_root_partition()
flags: list[str] = []
if root_part and root_part.fs_type:
flags.append(root_part.fs_type.value)
if disk_config.disk_encryption:
flags.append(tr('LUKS'))
disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path)
rows.append((tr('Disks'), ', '.join(disk_parts)))
for key, value in self._config.plain_cfg().items():
cfg[key.text()] = value
bl_config = self._config.bootloader_config
if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER:
rows.append((tr('Bootloader'), bl_config.bootloader.value))
for config_type, obj in self._config.sub_cfg().items():
if not hasattr(obj, 'summary'):
continue
kernels = self._config.kernels
if kernels:
rows.append((tr('Kernel'), ', '.join(kernels)))
summary = obj.summary()
if summary:
cfg[config_type.text()] = summary
profile_config = self._config.profile_config
if profile_config and profile_config.profile:
names = profile_config.profile.current_selection_names()
rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name))
if profile_config.greeter:
rows.append((tr('Greeter'), profile_config.greeter.value))
simple_summary = as_key_value_pair(cfg, ignore_empty=True)
packages = self._config.packages
if packages:
rows.append((tr('Packages'), str(len(packages))))
net_config = self._config.network_config
if isinstance(net_config, NetworkConfiguration):
rows.append((tr('Network'), net_config.type.display_msg()))
locale_config = self._config.locale_config
if locale_config:
rows.append((tr('Locale'), locale_config.sys_lang))
tz = self._config.timezone
if tz:
rows.append((tr('Timezone'), tz))
if not rows:
return ''
label_width = max(len(label) for label, _ in rows) + 2
return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows)
return simple_summary
async def confirm_config(self, show_install_warnings: bool = False) -> bool:
header = f'{tr("The specified configuration will be applied")}. '

View File

@ -1,7 +1,8 @@
from dataclasses import dataclass
from enum import StrEnum, auto
from typing import Any, NotRequired, Self, TypedDict
from typing import Any, NotRequired, Self, TypedDict, override
from archinstall.lib.models.config import SubConfig
from archinstall.lib.translationhandler import tr
@ -168,7 +169,7 @@ class FontsConfiguration:
@dataclass(frozen=True)
class ZramConfiguration:
class ZramConfiguration(SubConfig):
enabled: bool
algorithm: ZramAlgorithm = ZramAlgorithm.ZSTD
@ -181,9 +182,28 @@ class ZramConfiguration:
algo = arg.get('algorithm', arg.get('algo', ZramAlgorithm.ZSTD.value))
return cls(enabled=enabled, algorithm=ZramAlgorithm(algo))
@override
def json(self) -> dict[str, bool | str]:
return {
'enabled': self.enabled,
'algorithm': self.algorithm.value,
}
@override
def summary(self) -> list[str] | None:
out: list[str] = []
if self.enabled:
out.append(tr('Zram enabled'))
out.append(tr('Zram algorithm {}').format(self.algorithm))
return out
return None
@dataclass
class ApplicationConfiguration:
class ApplicationConfiguration(SubConfig):
bluetooth_config: BluetoothConfiguration | None = None
audio_config: AudioConfiguration | None = None
power_management_config: PowerManagementConfiguration | None = None
@ -223,6 +243,7 @@ class ApplicationConfiguration:
return app_config
@override
def json(self) -> ApplicationSerialization:
config: ApplicationSerialization = {}
@ -245,3 +266,28 @@ class ApplicationConfiguration:
config['fonts_config'] = self.fonts_config.json()
return config
@override
def summary(self) -> list[str]:
out: list[str] = []
if self.bluetooth_config and self.bluetooth_config.enabled:
out.append(tr('Bluetooth enabled'))
if self.audio_config:
out.append(tr('Audio server "{}"').format(self.audio_config.audio))
if self.power_management_config:
out.append(tr('Power management "{}"').format(self.power_management_config.power_management))
if self.print_service_config and self.print_service_config.enabled:
out.append(tr('Print service enabled'))
if self.firewall_config:
out.append(tr('Firewall "{}"').format(self.firewall_config.firewall))
if self.fonts_config and self.fonts_config.fonts:
fonts = ', '.join(f.value for f in self.fonts_config.fonts)
out.append(tr('Extra fonts "{}"').format(fonts))
return out

View File

@ -1,7 +1,8 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, NotRequired, Self, TypedDict
from typing import Any, NotRequired, Self, TypedDict, override
from archinstall.lib.models.config import SubConfig
from archinstall.lib.models.users import Password, User
from archinstall.lib.translationhandler import tr
@ -58,7 +59,7 @@ class U2FLoginConfiguration:
@dataclass
class AuthenticationConfiguration:
class AuthenticationConfiguration(SubConfig):
root_enc_password: Password | None = None
users: list[User] = field(default_factory=list)
u2f_config: U2FLoginConfiguration | None = None
@ -75,6 +76,7 @@ class AuthenticationConfiguration:
return auth_config
@override
def json(self) -> AuthenticationSerialization:
config: AuthenticationSerialization = {}
@ -83,6 +85,21 @@ class AuthenticationConfiguration:
return config
@override
def summary(self) -> list[str]:
out: list[str] = []
if self.root_enc_password:
out.append(tr('Root password set'))
if self.users:
out.append(tr('Configured {} user(s)').format(len(self.users)))
if self.u2f_config:
out.append(tr('U2F set up'))
return out
def has_superuser(self) -> bool:
return any(u.sudo for u in self.users)

View File

@ -1,8 +1,9 @@
import sys
from dataclasses import dataclass
from enum import Enum
from typing import Any, Self
from typing import Any, Self, override
from archinstall.lib.models.config import SubConfig
from archinstall.lib.output import warn
from archinstall.lib.translationhandler import tr
@ -60,14 +61,26 @@ class Bootloader(Enum):
@dataclass
class BootloaderConfiguration:
class BootloaderConfiguration(SubConfig):
bootloader: Bootloader
uki: bool = False
removable: bool = True
@override
def json(self) -> dict[str, Any]:
return {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable}
@override
def summary(self) -> list[str]:
out = [tr('Bootloader "{}"').format(self.bootloader.value)]
if self.uki:
out.append(tr('UKI enabled'))
if self.removable:
out.append(tr('Removable'))
return out
@classmethod
def parse_arg(cls, config: dict[str, Any], skip_boot: bool) -> Self:
bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot)

View File

@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
from typing import Any
class SubConfig(ABC):
@abstractmethod
def json(self) -> Any:
pass
@abstractmethod
def summary(self) -> str | list[str] | None:
pass

View File

@ -12,6 +12,7 @@ from parted import Disk, Geometry, Partition
from pydantic import BaseModel, Field, ValidationInfo, field_serializer, field_validator
from archinstall.lib.hardware import SysInfo
from archinstall.lib.models.config import SubConfig
from archinstall.lib.models.users import Password
from archinstall.lib.output import debug
from archinstall.lib.translationhandler import tr
@ -34,6 +35,15 @@ class DiskLayoutType(Enum):
case DiskLayoutType.Pre_mount:
return tr('Pre-mounted configuration')
def short_msg(self) -> str:
match self:
case DiskLayoutType.Default:
return tr('Default')
case DiskLayoutType.Manual:
return tr('Manual')
case DiskLayoutType.Pre_mount:
return tr('Pre-mount')
class _DiskLayoutConfigurationSerialization(TypedDict):
config_type: str
@ -45,7 +55,7 @@ class _DiskLayoutConfigurationSerialization(TypedDict):
@dataclass
class DiskLayoutConfiguration:
class DiskLayoutConfiguration(SubConfig):
config_type: DiskLayoutType
device_modifications: list[DeviceModification] = field(default_factory=list)
lvm_config: LvmConfiguration | None = None
@ -55,6 +65,7 @@ class DiskLayoutConfiguration:
# used for pre-mounted config
mountpoint: Path | None = None
@override
def json(self) -> _DiskLayoutConfigurationSerialization:
if self.config_type == DiskLayoutType.Pre_mount:
return {
@ -78,6 +89,28 @@ class DiskLayoutConfiguration:
return config
@override
def summary(self) -> list[str]:
out = [tr('{} layout').format(self.config_type.short_msg())]
devices = set(mod.device_path for mod in self.device_modifications)
if devices:
dev_str = ', '.join(str(d) for d in devices)
out.append(tr('Devices {}').format(dev_str))
if self.lvm_config is not None:
out.append(tr('LVM set up'))
if self.disk_encryption is not None:
out.append(tr('{} encryption').format(self.disk_encryption.encryption_type.type_to_text()))
if self.btrfs_options is not None:
if self.btrfs_options.snapshot_config:
out.append(tr('Btrfs snapshot "{}"').format(self.btrfs_options.snapshot_config.snapshot_type))
return out
@classmethod
def parse_arg(
cls,
@ -1321,7 +1354,7 @@ class _SnapshotConfigSerialization(TypedDict):
type: str
class SnapshotType(Enum):
class SnapshotType(StrEnum):
Snapper = 'Snapper'
Timeshift = 'Timeshift'

View File

@ -1,12 +1,13 @@
from dataclasses import dataclass
from typing import Any, Self
from typing import Any, Self, override
from archinstall.lib.locale.utils import get_kb_layout
from archinstall.lib.models.config import SubConfig
from archinstall.lib.translationhandler import tr
@dataclass
class LocaleConfiguration:
class LocaleConfiguration(SubConfig):
kb_layout: str
sys_lang: str
sys_enc: str
@ -23,6 +24,7 @@ class LocaleConfiguration:
layout = 'us'
return cls(layout, 'en_US.UTF-8', 'UTF-8')
@override
def json(self) -> dict[str, str]:
return {
'kb_layout': self.kb_layout,
@ -31,6 +33,15 @@ class LocaleConfiguration:
'console_font': self.console_font,
}
@override
def summary(self) -> list[str]:
return [
tr('Keyboard layout "{}"').format(self.kb_layout),
tr('Locale language "{}"').format(self.sys_lang),
tr('Locale encoding "{}"').format(self.sys_enc),
tr('Console font "{}"').format(self.console_font),
]
def preview(self) -> str:
output = '{}: {}\n'.format(tr('Keyboard layout'), self.kb_layout)
output += '{}: {}\n'.format(tr('Locale language'), self.sys_lang)

View File

@ -9,9 +9,11 @@ from typing import TYPE_CHECKING, Any, Self, TypedDict, override
from pydantic import BaseModel, ValidationInfo, field_validator, model_validator
from archinstall.lib.models.config import SubConfig
from archinstall.lib.models.packages import Repository
from archinstall.lib.networking import DownloadTimer, ping
from archinstall.lib.output import debug
from archinstall.lib.translationhandler import tr
if TYPE_CHECKING:
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
@ -236,7 +238,7 @@ class _MirrorConfigurationSerialization(TypedDict):
@dataclass
class MirrorConfiguration:
class MirrorConfiguration(SubConfig):
mirror_regions: list[MirrorRegion] = field(default_factory=list)
custom_servers: list[CustomServer] = field(default_factory=list)
optional_repositories: list[Repository] = field(default_factory=list)
@ -250,6 +252,7 @@ class MirrorConfiguration:
def custom_server_urls(self) -> str:
return '\n'.join(s.url for s in self.custom_servers)
@override
def json(self) -> _MirrorConfigurationSerialization:
regions = {}
for m in self.mirror_regions:
@ -262,6 +265,24 @@ class MirrorConfiguration:
'custom_repositories': [c.json() for c in self.custom_repositories],
}
@override
def summary(self) -> list[str]:
out: list[str] = []
if self.mirror_regions:
out.append(tr('Mirror regions "{}"').format(', '.join(m.name for m in self.mirror_regions)))
if self.optional_repositories:
out.append(tr('Optional repositories "{}"').format(', '.join(r.value for r in self.optional_repositories)))
if self.custom_servers:
out.append(tr('Custom servers set up'))
if self.custom_repositories:
out.append(tr('Custom repositories set up'))
return out
def custom_servers_config(self) -> str:
config = ''

View File

@ -3,6 +3,7 @@ from dataclasses import dataclass, field
from enum import Enum
from typing import NotRequired, Self, TypedDict, override
from archinstall.lib.models.config import SubConfig
from archinstall.lib.output import debug
from archinstall.lib.translationhandler import tr
@ -103,10 +104,11 @@ class _NetworkConfigurationSerialization(TypedDict):
@dataclass
class NetworkConfiguration:
class NetworkConfiguration(SubConfig):
type: NicType
nics: list[Nic] = field(default_factory=list)
@override
def json(self) -> _NetworkConfigurationSerialization:
config: _NetworkConfigurationSerialization = {'type': self.type.value}
if self.nics:
@ -114,6 +116,10 @@ class NetworkConfiguration:
return config
@override
def summary(self) -> str:
return self.type.display_msg()
@classmethod
def parse_arg(cls, config: _NetworkConfigurationSerialization) -> Self | None:
nic_type = config.get('type', None)

View File

@ -1,6 +1,7 @@
from dataclasses import dataclass
from typing import Self, TypedDict
from typing import Self, TypedDict, override
from archinstall.lib.models.config import SubConfig
from archinstall.lib.translationhandler import tr
@ -10,7 +11,7 @@ class PacmanConfigSerialization(TypedDict):
@dataclass
class PacmanConfiguration:
class PacmanConfiguration(SubConfig):
parallel_downloads: int = 5
color: bool = True
@ -18,12 +19,19 @@ class PacmanConfiguration:
def default(cls) -> Self:
return cls()
@override
def json(self) -> PacmanConfigSerialization:
return {
'parallel_downloads': self.parallel_downloads,
'color': self.color,
}
@override
def summary(self) -> str | None:
if self.color:
return tr('Color enabled')
return None
def preview(self) -> str:
color_str = str(self.color)
output = '{}: {}\n'.format(tr('Parallel Downloads'), self.parallel_downloads)

View File

@ -1,8 +1,10 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Self, TypedDict
from typing import TYPE_CHECKING, Self, TypedDict, override
from archinstall.default_profiles.profile import GreeterType, Profile
from archinstall.lib.hardware import GfxDriver
from archinstall.lib.models.config import SubConfig
from archinstall.lib.translationhandler import tr
if TYPE_CHECKING:
from archinstall.lib.profile.profiles_handler import ProfileSerialization
@ -15,11 +17,12 @@ class _ProfileConfigurationSerialization(TypedDict):
@dataclass
class ProfileConfiguration:
class ProfileConfiguration(SubConfig):
profile: Profile | None = None
gfx_driver: GfxDriver | None = None
greeter: GreeterType | None = None
@override
def json(self) -> _ProfileConfigurationSerialization:
from archinstall.lib.profile.profiles_handler import profile_handler
@ -29,6 +32,23 @@ class ProfileConfiguration:
'greeter': self.greeter.value if self.greeter else None,
}
@override
def summary(self) -> list[str] | None:
out: list[str] = []
if self.profile:
out.append(self.profile.name)
if self.gfx_driver:
out.append(tr('{} grphics driver').format(self.gfx_driver.value))
if self.greeter:
out.append(tr('{} greeter').format(self.greeter.value))
return out
return None
@classmethod
def parse_arg(cls, arg: _ProfileConfigurationSerialization) -> Self:
from archinstall.lib.profile.profiles_handler import profile_handler

View File

@ -11,6 +11,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, Any
from archinstall.lib.utils.encoding import unicode_ljust, unicode_rjust
from archinstall.tui.rich import BaseRichTable
if TYPE_CHECKING:
from _typeshed import DataclassInstance
@ -20,7 +21,7 @@ class FormattedOutput:
@staticmethod
def _get_values(
o: DataclassInstance,
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
class_formatter: str | Callable | None = None, # type: ignore[type-arg] # pyright: ignore[reportMissingTypeArgument]
filter_list: list[str] = [],
) -> dict[str, Any]:
"""
@ -129,6 +130,35 @@ class FormattedOutput:
return output
def as_key_value_pair(
entries: dict[str, str | list[str] | bool],
ignore_empty: bool = True,
) -> str:
"""
Formats key-values as a Rich Table:
key1 : value1
key2 : value2
...
"""
table = BaseRichTable()
table.add_column('key', style='bold', no_wrap=True)
table.add_column('value', style='white', max_width=70)
for label, value in entries.items():
if ignore_empty and not value:
continue
if isinstance(value, bool):
value = 'Yes' if value else 'No'
if isinstance(value, list):
value = '\n '.join(str(val) for val in value)
table.add_row(label.title(), f': {value}')
return table.stringify()
class Journald:
@staticmethod
def log(message: str, level: int = logging.DEBUG) -> None:

View File

@ -350,11 +350,6 @@ class OptionListScreen(BaseScreen[ValueT]):
]
)
# debug(f'Index: {index}')
# debug(f'Region: {option_list.region}')
# debug(f'Scroll offset: {option_list.scroll_offset}')
# debug(f'Target_Y: {target_y}')
self.app.cursor_position = Offset(option_list.region.x, target_y)
self.app.refresh()
@ -1163,8 +1158,6 @@ class TableSelectionScreen(BaseScreen[ValueT]):
]
)
debug(f'Setting cursor to target_y: {target_y}')
self.app.cursor_position = Offset(data_table.region.x, target_y)
self.app.refresh()

25
archinstall/tui/rich.py Normal file
View File

@ -0,0 +1,25 @@
from io import StringIO
from rich import box
from rich.console import Console
from rich.table import Table as RichTable
class BaseRichTable(RichTable):
def __init__(self) -> None:
super().__init__(
box=box.SIMPLE,
show_header=False,
pad_edge=False,
show_edge=False,
)
def stringify(self) -> str:
string_io = StringIO()
buf = Console(file=string_io, highlight=False)
buf.print(self)
_ = string_io.seek(0)
output = string_io.read()
return output