Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Girtler <blackrabbit256@gmail.com>2023-06-05 18:02:49 +1000
committerGitHub <noreply@github.com>2023-06-05 10:02:49 +0200
commit06eadb31d4f0bca0c8cb95b6a9eb62ddd2d7cff2 (patch)
tree07a7ed675d125703346fa343f1aa9e5e4129dd5f
parent5276d95339368210e75791e2b88c1bf5aca4517b (diff)
Move locales and cleanup menu (#1814)
* Cleanup imports and unused code * Cleanup imports and unused code * Update build check * Keep deprecation exception * Simplify logging * Move locale into new sub-menu --------- Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
-rw-r--r--archinstall/__init__.py3
-rw-r--r--archinstall/default_profiles/profile.py19
-rw-r--r--archinstall/lib/global_menu.py95
-rw-r--r--archinstall/lib/hardware.py2
-rw-r--r--archinstall/lib/installer.py38
-rw-r--r--archinstall/lib/interactions/__init__.py3
-rw-r--r--archinstall/lib/interactions/general_conf.py12
-rw-r--r--archinstall/lib/interactions/locale_conf.py43
-rw-r--r--archinstall/lib/interactions/system_conf.py6
-rw-r--r--archinstall/lib/locale/__init__.py6
-rw-r--r--archinstall/lib/locale/locale.py68
-rw-r--r--archinstall/lib/locale/locale_menu.py155
-rw-r--r--archinstall/lib/menu/abstract_menu.py38
-rw-r--r--archinstall/lib/utils/util.py23
-rw-r--r--archinstall/scripts/guided.py17
-rw-r--r--archinstall/scripts/swiss.py16
-rw-r--r--examples/interactive_installation.py17
17 files changed, 374 insertions, 187 deletions
diff --git a/archinstall/__init__.py b/archinstall/__init__.py
index e6fcb267..ce58e255 100644
--- a/archinstall/__init__.py
+++ b/archinstall/__init__.py
@@ -213,8 +213,7 @@ def load_config():
"""
from .lib.models import NetworkConfiguration
- arguments.setdefault('sys-language', 'en_US')
- arguments.setdefault('sys-encoding', 'utf-8')
+ arguments['locale_config'] = locale.LocaleConfiguration.parse_arg(arguments)
if (archinstall_lang := arguments.get('archinstall-language', None)) is not None:
arguments['archinstall-language'] = TranslationHandler().get_language_by_name(archinstall_lang)
diff --git a/archinstall/default_profiles/profile.py b/archinstall/default_profiles/profile.py
index b1ad1f50..ce07c286 100644
--- a/archinstall/default_profiles/profile.py
+++ b/archinstall/default_profiles/profile.py
@@ -3,7 +3,7 @@ from __future__ import annotations
from enum import Enum, auto
from typing import List, Optional, Any, Dict, TYPE_CHECKING, TypeVar
-from archinstall.lib.output import FormattedOutput
+from archinstall.lib.utils.util import format_cols
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
@@ -185,17 +185,6 @@ class Profile:
return None
def packages_text(self) -> str:
- text = str(_('Installed packages')) + ':\n'
-
- nr_packages = len(self.packages)
- if nr_packages <= 5:
- col = 1
- elif nr_packages <= 10:
- col = 2
- elif nr_packages <= 15:
- col = 3
- else:
- col = 4
-
- text += FormattedOutput.as_columns(self.packages, col)
- return text
+ header = str(_('Installed packages'))
+ output = format_cols(self.packages, header)
+ return output
diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py
index fc58a653..91ebc6a0 100644
--- a/archinstall/lib/global_menu.py
+++ b/archinstall/lib/global_menu.py
@@ -4,6 +4,7 @@ from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING
from . import disk
from .general import secret
+from .locale.locale_menu import LocaleConfiguration, LocaleMenu
from .menu import Selector, AbstractMenu
from .mirrors import MirrorConfiguration, MirrorMenu
from .models import NetworkConfiguration
@@ -24,9 +25,7 @@ from .interactions import ask_to_configure_network
from .interactions import get_password, ask_for_a_timezone
from .interactions import select_additional_repositories
from .interactions import select_kernel
-from .interactions import select_language
-from .interactions import select_locale_enc
-from .interactions import select_locale_lang
+from .utils.util import format_cols
from .interactions import ask_ntp
from .interactions.disk_conf import select_disk_config
@@ -36,6 +35,7 @@ if TYPE_CHECKING:
class GlobalMenu(AbstractMenu):
def __init__(self, data_store: Dict[str, Any]):
+ self._defined_text = str(_('Defined'))
super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3)
def setup_selection_menu_options(self):
@@ -46,28 +46,19 @@ class GlobalMenu(AbstractMenu):
lambda x: self._select_archinstall_language(x),
display_func=lambda x: x.display_name,
default=self.translation_handler.get_language_by_abbr('en'))
- self._menu_options['keyboard-layout'] = \
+ self._menu_options['locale_config'] = \
Selector(
- _('Keyboard layout'),
- lambda preset: select_language(preset),
- default='us')
+ _('Locales'),
+ lambda preset: self._locale_selection(preset),
+ preview_func=self._prev_locale,
+ display_func=lambda x: self._defined_text if x else '')
self._menu_options['mirror_config'] = \
Selector(
_('Mirrors'),
lambda preset: self._mirror_configuration(preset),
- display_func=lambda x: str(_('Defined')) if x else '',
+ display_func=lambda x: self._defined_text if x else '',
preview_func=self._prev_mirror_config
)
- self._menu_options['sys-language'] = \
- Selector(
- _('Locale language'),
- lambda preset: select_locale_lang(preset),
- default='en_US')
- self._menu_options['sys-encoding'] = \
- Selector(
- _('Locale encoding'),
- lambda preset: select_locale_enc(preset),
- default='UTF-8')
self._menu_options['disk_config'] = \
Selector(
_('Disk configuration'),
@@ -103,32 +94,32 @@ class GlobalMenu(AbstractMenu):
Selector(
_('Root password'),
lambda preset:self._set_root_password(),
- display_func=lambda x: secret(x) if x else 'None')
+ display_func=lambda x: secret(x) if x else '')
self._menu_options['!users'] = \
Selector(
_('User account'),
lambda x: self._create_user_account(x),
default=[],
- display_func=lambda x: f'{len(x)} {_("User(s)")}' if len(x) > 0 else None,
+ display_func=lambda x: f'{len(x)} {_("User(s)")}' if len(x) > 0 else '',
preview_func=self._prev_users)
self._menu_options['profile_config'] = \
Selector(
_('Profile'),
lambda preset: self._select_profile(preset),
- display_func=lambda x: x.profile.name if x else 'None',
+ display_func=lambda x: x.profile.name if x else '',
preview_func=self._prev_profile
)
self._menu_options['audio'] = \
Selector(
_('Audio'),
lambda preset: self._select_audio(preset),
- display_func=lambda x: x if x else 'None',
+ display_func=lambda x: x if x else '',
default=None
)
self._menu_options['parallel downloads'] = \
Selector(
_('Parallel Downloads'),
- add_number_of_parrallel_downloads,
+ lambda preset: add_number_of_parrallel_downloads(preset),
display_func=lambda x: x if x else '0',
default=0
)
@@ -141,19 +132,20 @@ class GlobalMenu(AbstractMenu):
self._menu_options['packages'] = \
Selector(
_('Additional packages'),
- # lambda x: ask_additional_packages_to_install(storage['arguments'].get('packages', None)),
- ask_additional_packages_to_install,
+ lambda preset: ask_additional_packages_to_install(preset),
+ display_func=lambda x: self._defined_text if x else '',
+ preview_func=self._prev_additional_pkgs,
default=[])
self._menu_options['additional-repositories'] = \
Selector(
_('Optional repositories'),
- select_additional_repositories,
+ lambda preset: select_additional_repositories(preset),
display_func=lambda x: ', '.join(x) if x else None,
default=[])
self._menu_options['nic'] = \
Selector(
_('Network configuration'),
- ask_to_configure_network,
+ lambda preset: ask_to_configure_network(preset),
display_func=lambda x: self._display_network_conf(x),
preview_func=self._prev_network_config,
default={})
@@ -177,12 +169,37 @@ class GlobalMenu(AbstractMenu):
self._menu_options['install'] = \
Selector(
self._install_text(),
- exec_func=lambda n,v: True if len(self._missing_configs()) == 0 else False,
+ exec_func=lambda n, v: True if len(self._missing_configs()) == 0 else False,
preview_func=self._prev_install_missing_config,
no_store=True)
self._menu_options['abort'] = Selector(_('Abort'), exec_func=lambda n,v:exit(1))
+ def _missing_configs(self) -> List[str]:
+ def check(s):
+ return self._menu_options.get(s).has_selection()
+
+ def has_superuser() -> bool:
+ sel = self._menu_options['!users']
+ if sel.current_selection:
+ return any([u.sudo for u in sel.current_selection])
+ return False
+
+ mandatory_fields = dict(filter(lambda x: x[1].is_mandatory(), self._menu_options.items()))
+ missing = set()
+
+ for key, selector in mandatory_fields.items():
+ if key in ['!root-password', '!users']:
+ if not check('!root-password') and not has_superuser():
+ missing.add(
+ str(_('Either root-password or at least 1 user with sudo privileges must be specified'))
+ )
+ elif key == 'disk_config':
+ if not check('disk_config'):
+ missing.add(self._menu_options['disk_config'].description)
+
+ return list(missing)
+
def _update_install_text(self, name: str, value: str):
text = self._install_text()
self._menu_options['install'].update_description(text)
@@ -216,6 +233,21 @@ class GlobalMenu(AbstractMenu):
disk_encryption = disk.DiskEncryptionMenu(mods, data_store, preset=preset).run()
return disk_encryption
+ def _locale_selection(self, preset: LocaleConfiguration) -> LocaleConfiguration:
+ data_store: Dict[str, Any] = {}
+ locale_config = LocaleMenu(data_store, preset).run()
+ return locale_config
+
+ def _prev_locale(self) -> Optional[str]:
+ selector = self._menu_options['locale_config']
+ if selector.has_selection():
+ config: LocaleConfiguration = selector.current_selection # type: ignore
+ output = '{}: {}\n'.format(str(_('Keyboard layout')), config.kb_layout)
+ output += '{}: {}\n'.format(str(_('Locale language')), config.sys_lang)
+ output += '{}: {}'.format(str(_('Locale encoding')), config.sys_enc)
+ return output
+ return None
+
def _prev_network_config(self) -> Optional[str]:
selector = self._menu_options['nic']
if selector.has_selection():
@@ -224,6 +256,13 @@ class GlobalMenu(AbstractMenu):
return FormattedOutput.as_table(ifaces)
return None
+ def _prev_additional_pkgs(self):
+ selector = self._menu_options['packages']
+ if selector.has_selection():
+ packages: List[str] = selector.current_selection
+ return format_cols(packages, None)
+ return None
+
def _prev_disk_layouts(self) -> Optional[str]:
selector = self._menu_options['disk_config']
disk_layout_conf: Optional[disk.DiskLayoutConfiguration] = selector.current_selection
diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py
index 220d3d37..2b65e07c 100644
--- a/archinstall/lib/hardware.py
+++ b/archinstall/lib/hardware.py
@@ -3,9 +3,9 @@ from functools import cached_property
from pathlib import Path
from typing import Optional, Dict, List
+from .exceptions import SysCallError
from .general import SysCommand
from .networking import list_interfaces, enrich_iface_types
-from .exceptions import SysCallError
from .output import debug
AVAILABLE_GFX_DRIVERS = {
diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py
index 30442774..6eac85fc 100644
--- a/archinstall/lib/installer.py
+++ b/archinstall/lib/installer.py
@@ -12,6 +12,7 @@ from . import disk
from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError
from .general import SysCommand
from .hardware import SysInfo
+from .locale import LocaleConfiguration
from .locale import verify_keyboard_layout, verify_x11_keyboard_layout
from .luks import Luks2
from .mirrors import use_mirrors, MirrorConfiguration, add_custom_mirrors
@@ -457,37 +458,36 @@ class Installer:
with open(f'{self.target}/etc/hostname', 'w') as fh:
fh.write(hostname + '\n')
- def set_locale(self, locale :str, encoding :str = 'UTF-8', *args :str, **kwargs :str) -> bool:
- if not len(locale):
- return True
-
+ def set_locale(self, locale_config: LocaleConfiguration):
modifier = ''
+ lang = locale_config.sys_lang
+ encoding = locale_config.sys_enc
# This is a temporary patch to fix #1200
- if '.' in locale:
- locale, potential_encoding = locale.split('.', 1)
+ if '.' in locale_config.sys_lang:
+ lang, potential_encoding = locale_config.sys_lang.split('.', 1)
# Override encoding if encoding is set to the default parameter
# and the "found" encoding differs.
- if encoding == 'UTF-8' and encoding != potential_encoding:
+ if locale_config.sys_enc == 'UTF-8' and locale_config.sys_enc != potential_encoding:
encoding = potential_encoding
# Make sure we extract the modifier, that way we can put it in if needed.
- if '@' in locale:
- locale, modifier = locale.split('@', 1)
+ if '@' in locale_config.sys_lang:
+ lang, modifier = locale_config.sys_lang.split('@', 1)
modifier = f"@{modifier}"
# - End patch
with open(f'{self.target}/etc/locale.gen', 'a') as fh:
- fh.write(f'{locale}.{encoding}{modifier} {encoding}\n')
+ fh.write(f'{lang}.{encoding}{modifier} {encoding}\n')
+
with open(f'{self.target}/etc/locale.conf', 'w') as fh:
- fh.write(f'LANG={locale}.{encoding}{modifier}\n')
+ fh.write(f'LANG={lang}.{encoding}{modifier}\n')
try:
SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen')
- return True
- except SysCallError:
- return False
+ except SysCallError as e:
+ error(f'Failed to run locale-gen on target: {e}')
def set_timezone(self, zone :str, *args :str, **kwargs :str) -> bool:
if not zone:
@@ -620,7 +620,7 @@ class Installer:
return True
- def mkinitcpio(self, *flags :str) -> bool:
+ def mkinitcpio(self, flags: List[str], locale_config: LocaleConfiguration) -> bool:
for plugin in plugins.values():
if hasattr(plugin, 'on_mkinitcpio'):
# Allow plugins to override the usage of mkinitcpio altogether.
@@ -630,7 +630,7 @@ class Installer:
# mkinitcpio will error out if there's no vconsole.
if (vconsole := Path(f"{self.target}/etc/vconsole.conf")).exists() is False:
with vconsole.open('w') as fh:
- fh.write(f"KEYMAP={storage['arguments']['keyboard-layout']}\n")
+ fh.write(f"KEYMAP={locale_config.kb_layout}\n")
with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit:
mkinit.write(f"MODULES=({' '.join(self.modules)})\n")
@@ -658,7 +658,7 @@ class Installer:
testing: bool = False,
multilib: bool = False,
hostname: str = 'archinstall',
- locales: List[str] = ['en_US.UTF-8 UTF-8']
+ locale_config: LocaleConfiguration = LocaleConfiguration.default()
):
for mod in self._disk_config.device_modifications:
for part in mod.partitions:
@@ -734,12 +734,12 @@ class Installer:
# sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime')
# sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')
self.set_hostname(hostname)
- self.set_locale(*locales[0].split())
+ self.set_locale(locale_config)
# TODO: Use python functions for this
SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root')
- self.mkinitcpio('-P')
+ self.mkinitcpio(['-P'], locale_config)
self.helper_flags['base'] = True
diff --git a/archinstall/lib/interactions/__init__.py b/archinstall/lib/interactions/__init__.py
index 158750cc..466cfa0b 100644
--- a/archinstall/lib/interactions/__init__.py
+++ b/archinstall/lib/interactions/__init__.py
@@ -1,4 +1,3 @@
-from .locale_conf import select_locale_lang, select_locale_enc
from .manage_users_conf import UserList, ask_for_additional_users
from .network_conf import ManualNetworkConfig, ask_to_configure_network
from .utils import get_password
@@ -10,7 +9,7 @@ from .disk_conf import (
)
from .general_conf import (
- ask_ntp, ask_hostname, ask_for_a_timezone, ask_for_audio_selection, select_language,
+ ask_ntp, ask_hostname, ask_for_a_timezone, ask_for_audio_selection,
select_archinstall_language, ask_additional_packages_to_install,
add_number_of_parrallel_downloads, select_additional_repositories
)
diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py
index 0338c61e..3b78847b 100644
--- a/archinstall/lib/interactions/general_conf.py
+++ b/archinstall/lib/interactions/general_conf.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import pathlib
from typing import List, Any, Optional, TYPE_CHECKING
-from ..locale import list_keyboard_languages, list_timezones
+from ..locale import list_timezones, list_keyboard_languages
from ..menu import MenuSelectionType, Menu, TextInput
from ..output import warn
from ..packages.packages import validate_package_list
@@ -119,18 +119,18 @@ def select_archinstall_language(languages: List[Language], preset: Language) ->
raise ValueError('Language selection not handled')
-def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List[str]:
+def ask_additional_packages_to_install(preset: List[str] = []) -> List[str]:
# Additional packages (with some light weight error handling for invalid package names)
print(_('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.'))
print(_('If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.'))
- def read_packages(already_defined: list = []) -> list:
- display = ' '.join(already_defined)
+ def read_packages(p: List = []) -> list:
+ display = ' '.join(p)
input_packages = TextInput(_('Write additional packages to install (space separated, leave blank to skip): '), display).run().strip()
return input_packages.split() if input_packages else []
- pre_set_packages = pre_set_packages if pre_set_packages else []
- packages = read_packages(pre_set_packages)
+ preset = preset if preset else []
+ packages = read_packages(preset)
if not storage['arguments']['offline'] and not storage['arguments']['no_pkg_lookups']:
while True:
diff --git a/archinstall/lib/interactions/locale_conf.py b/archinstall/lib/interactions/locale_conf.py
deleted file mode 100644
index de115202..00000000
--- a/archinstall/lib/interactions/locale_conf.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from typing import Any, TYPE_CHECKING, Optional
-
-from ..locale import list_locales
-from ..menu import Menu, MenuSelectionType
-
-if TYPE_CHECKING:
- _: Any
-
-
-def select_locale_lang(preset: Optional[str] = None) -> Optional[str]:
- locales = list_locales()
- locale_lang = set([locale.split()[0] for locale in locales])
-
- choice = Menu(
- _('Choose which locale language to use'),
- list(locale_lang),
- sort=True,
- preset_values=preset
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Selection: return choice.single_value
- case MenuSelectionType.Skip: return preset
-
- return None
-
-
-def select_locale_enc(preset: Optional[str] = None) -> Optional[str]:
- locales = list_locales()
- locale_enc = set([locale.split()[1] for locale in locales])
-
- choice = Menu(
- _('Choose which locale encoding to use'),
- list(locale_enc),
- sort=True,
- preset_values=preset
- ).run()
-
- match choice.type_:
- case MenuSelectionType.Selection: return choice.single_value
- case MenuSelectionType.Skip: return preset
-
- return None
diff --git a/archinstall/lib/interactions/system_conf.py b/archinstall/lib/interactions/system_conf.py
index bbcb5b23..ea7e5989 100644
--- a/archinstall/lib/interactions/system_conf.py
+++ b/archinstall/lib/interactions/system_conf.py
@@ -29,14 +29,14 @@ def select_kernel(preset: List[str] = []) -> List[str]:
sort=True,
multi=True,
preset_values=preset,
- allow_reset=True,
allow_reset_warning_msg=warning
).run()
match choice.type_:
case MenuSelectionType.Skip: return preset
- case MenuSelectionType.Reset: return []
- case MenuSelectionType.Selection: return choice.value # type: ignore
+ case MenuSelectionType.Selection: return choice.single_value
+
+ return []
def ask_for_bootloader(preset: Bootloader) -> Bootloader:
diff --git a/archinstall/lib/locale/__init__.py b/archinstall/lib/locale/__init__.py
new file mode 100644
index 00000000..6c32d6f3
--- /dev/null
+++ b/archinstall/lib/locale/__init__.py
@@ -0,0 +1,6 @@
+from .locale_menu import LocaleConfiguration
+from .locale import (
+ list_keyboard_languages, list_locales, list_x11_keyboard_languages,
+ verify_keyboard_layout, verify_x11_keyboard_layout, set_kb_layout,
+ list_timezones
+)
diff --git a/archinstall/lib/locale/locale.py b/archinstall/lib/locale/locale.py
new file mode 100644
index 00000000..c3294e83
--- /dev/null
+++ b/archinstall/lib/locale/locale.py
@@ -0,0 +1,68 @@
+from typing import Iterator, List
+
+from ..exceptions import ServiceException, SysCallError
+from ..general import SysCommand
+from ..output import error
+
+
+def list_keyboard_languages() -> Iterator[str]:
+ for line in SysCommand("localectl --no-pager list-keymaps", environment_vars={'SYSTEMD_COLORS': '0'}):
+ yield line.decode('UTF-8').strip()
+
+
+def list_locales() -> List[str]:
+ with open('/etc/locale.gen', 'r') as fp:
+ locales = []
+ # before the list of locales begins there's an empty line with a '#' in front
+ # so we'll collect the localels from bottom up and halt when we're donw
+ entries = fp.readlines()
+ entries.reverse()
+
+ for entry in entries:
+ text = entry.replace('#', '').strip()
+ if text == '':
+ break
+ locales.append(text)
+
+ locales.reverse()
+ return locales
+
+
+def list_x11_keyboard_languages() -> Iterator[str]:
+ for line in SysCommand("localectl --no-pager list-x11-keymap-layouts", environment_vars={'SYSTEMD_COLORS': '0'}):
+ yield line.decode('UTF-8').strip()
+
+
+def verify_keyboard_layout(layout :str) -> bool:
+ for language in list_keyboard_languages():
+ if layout.lower() == language.lower():
+ return True
+ return False
+
+
+def verify_x11_keyboard_layout(layout :str) -> bool:
+ for language in list_x11_keyboard_languages():
+ if layout.lower() == language.lower():
+ return True
+ return False
+
+
+def set_kb_layout(locale :str) -> bool:
+ if len(locale.strip()):
+ if not verify_keyboard_layout(locale):
+ error(f"Invalid keyboard locale specified: {locale}")
+ return False
+
+ try:
+ SysCommand(f'localectl set-keymap {locale}')
+ except SysCallError as err:
+ raise ServiceException(f"Unable to set locale '{locale}' for console: {err}")
+
+ return True
+
+ return False
+
+
+def list_timezones() -> Iterator[str]:
+ for line in SysCommand("timedatectl --no-pager list-timezones", environment_vars={'SYSTEMD_COLORS': '0'}):
+ yield line.decode('UTF-8').strip()
diff --git a/archinstall/lib/locale/locale_menu.py b/archinstall/lib/locale/locale_menu.py
new file mode 100644
index 00000000..29dd775d
--- /dev/null
+++ b/archinstall/lib/locale/locale_menu.py
@@ -0,0 +1,155 @@
+from dataclasses import dataclass
+from typing import Dict, Any, TYPE_CHECKING, Optional
+
+from .locale import set_kb_layout, list_keyboard_languages, list_locales
+from ..menu import Selector, AbstractSubMenu, MenuSelectionType, Menu
+
+if TYPE_CHECKING:
+ _: Any
+
+
+@dataclass
+class LocaleConfiguration:
+ kb_layout: str
+ sys_lang: str
+ sys_enc: str
+
+ @staticmethod
+ def default() -> 'LocaleConfiguration':
+ return LocaleConfiguration('us', 'en_US', 'UTF-8')
+
+ def json(self) -> Dict[str, str]:
+ return {
+ 'kb_layout': self.kb_layout,
+ 'sys_lang': self.sys_lang,
+ 'sys_enc': self.sys_enc
+ }
+
+ @classmethod
+ def _load_config(cls, config: 'LocaleConfiguration', args: Dict[str, Any]) -> 'LocaleConfiguration':
+ if 'sys_lang' in args:
+ config.sys_lang = args['sys_lang']
+ if 'sys_enc' in args:
+ config.sys_enc = args['sys_enc']
+ if 'kb_layout' in args:
+ config.kb_layout = args['kb_layout']
+
+ return config
+
+ @classmethod
+ def parse_arg(cls, args: Dict[str, Any]) -> 'LocaleConfiguration':
+ default = cls.default()
+
+ if 'locale_config' in args:
+ default = cls._load_config(default, args['locale_config'])
+ else:
+ default = cls._load_config(default, args)
+
+ return default
+
+
+class LocaleMenu(AbstractSubMenu):
+ def __init__(
+ self,
+ data_store: Dict[str, Any],
+ locele_conf: LocaleConfiguration
+ ):
+ self._preset = locele_conf
+ super().__init__(data_store=data_store)
+
+ def setup_selection_menu_options(self):
+ self._menu_options['keyboard-layout'] = \
+ Selector(
+ _('Keyboard layout'),
+ lambda preset: self._select_kb_layout(preset),
+ default='us',
+ enabled=True)
+ self._menu_options['sys-language'] = \
+ Selector(
+ _('Locale language'),
+ lambda preset: select_locale_lang(preset),
+ default='en_US',
+ enabled=True)
+ self._menu_options['sys-encoding'] = \
+ Selector(
+ _('Locale encoding'),
+ lambda preset: select_locale_enc(preset),
+ default='UTF-8',
+ enabled=True)
+
+ def run(self, allow_reset: bool = True) -> LocaleConfiguration:
+ super().run(allow_reset=allow_reset)
+
+ return LocaleConfiguration(
+ self._data_store['keyboard-layout'],
+ self._data_store['sys-language'],
+ self._data_store['sys-encoding']
+ )
+
+ def _select_kb_layout(self, preset: Optional[str]) -> Optional[str]:
+ kb_lang = select_kb_layout(preset)
+ if kb_lang:
+ set_kb_layout(kb_lang)
+ return kb_lang
+
+
+def select_locale_lang(preset: Optional[str] = None) -> Optional[str]:
+ locales = list_locales()
+ locale_lang = set([locale.split()[0] for locale in locales])
+
+ choice = Menu(
+ _('Choose which locale language to use'),
+ list(locale_lang),
+ sort=True,
+ preset_values=preset
+ ).run()
+
+ match choice.type_:
+ case MenuSelectionType.Selection: return choice.single_value
+ case MenuSelectionType.Skip: return preset
+
+ return None
+
+
+def select_locale_enc(preset: Optional[str] = None) -> Optional[str]:
+ locales = list_locales()
+ locale_enc = set([locale.split()[1] for locale in locales])
+
+ choice = Menu(
+ _('Choose which locale encoding to use'),
+ list(locale_enc),
+ sort=True,
+ preset_values=preset
+ ).run()
+
+ match choice.type_:
+ case MenuSelectionType.Selection: return choice.single_value
+ case MenuSelectionType.Skip: return preset
+
+ return None
+
+
+def select_kb_layout(preset: Optional[str] = None) -> Optional[str]:
+ """
+ Asks the user to select a language
+ Usually this is combined with :ref:`archinstall.list_keyboard_languages`.
+
+ :return: The language/dictionary key of the selected language
+ :rtype: str
+ """
+ kb_lang = list_keyboard_languages()
+ # sort alphabetically and then by length
+ sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len)
+
+ choice = Menu(
+ _('Select keyboard layout'),
+ sorted_kb_lang,
+ preset_values=preset,
+ sort=False
+ ).run()
+
+ match choice.type_:
+ case MenuSelectionType.Skip: return preset
+ case MenuSelectionType.Selection: return choice.single_value
+
+ return None
diff --git a/archinstall/lib/menu/abstract_menu.py b/archinstall/lib/menu/abstract_menu.py
index 2bd56374..eee99747 100644
--- a/archinstall/lib/menu/abstract_menu.py
+++ b/archinstall/lib/menu/abstract_menu.py
@@ -3,7 +3,6 @@ from __future__ import annotations
from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING
from .menu import Menu, MenuSelectionType
-from ..locale import set_keyboard_language
from ..output import error
from ..translationhandler import TranslationHandler, Language
@@ -130,7 +129,7 @@ class Selector:
if current:
padding += 5
description = str(self._description).ljust(padding, ' ')
- current = str(_('set: {}').format(current))
+ current = current
else:
description = self._description
current = ''
@@ -243,31 +242,6 @@ class AbstractMenu:
elif selector is not None and selector.has_selection():
self._data_store[selector_name] = selector.current_selection
- def _missing_configs(self) -> List[str]:
- def check(s):
- return self._menu_options.get(s).has_selection()
-
- def has_superuser() -> bool:
- sel = self._menu_options['!users']
- if sel.current_selection:
- return any([u.sudo for u in sel.current_selection])
- return False
-
- mandatory_fields = dict(filter(lambda x: x[1].is_mandatory(), self._menu_options.items()))
- missing = set()
-
- for key, selector in mandatory_fields.items():
- if key in ['!root-password', '!users']:
- if not check('!root-password') and not has_superuser():
- missing.add(
- str(_('Either root-password or at least 1 user with sudo privileges must be specified'))
- )
- elif key == 'disk_config':
- if not check('disk_config'):
- missing.add(self._menu_options['disk_config'].description)
-
- return list(missing)
-
def setup_selection_menu_options(self):
""" Define the menu options.
Menu options can be defined here in a subclass or done per program calling self.set_option()
@@ -328,9 +302,6 @@ class AbstractMenu:
cursor_pos = None
while True:
- # Before continuing, set the preferred keyboard layout/language in the current terminal.
- # This will just help the user with the next following questions.
- self._set_kb_language()
enabled_menus = self._menus_to_enable()
padding = self._get_menu_text_padding(list(enabled_menus.values()))
@@ -425,13 +396,6 @@ class AbstractMenu:
return True
- def _set_kb_language(self):
- """ general for ArchInstall"""
- # Before continuing, set the preferred keyboard layout/language in the current terminal.
- # This will just help the user with the next following questions.
- if self._data_store.get('keyboard-layout', None) and len(self._data_store['keyboard-layout']):
- set_keyboard_language(self._data_store['keyboard-layout'])
-
def _verify_selection_enabled(self, selection_name: str) -> bool:
""" general """
if selection := self._menu_options.get(selection_name, None):
diff --git a/archinstall/lib/utils/util.py b/archinstall/lib/utils/util.py
index 34716f4a..8df75ab1 100644
--- a/archinstall/lib/utils/util.py
+++ b/archinstall/lib/utils/util.py
@@ -1,6 +1,7 @@
from pathlib import Path
-from typing import Any, TYPE_CHECKING, Optional
+from typing import Any, TYPE_CHECKING, Optional, List
+from ..output import FormattedOutput
from ..output import info
if TYPE_CHECKING:
@@ -28,3 +29,23 @@ def is_subpath(first: Path, second: Path):
return True
except ValueError:
return False
+
+
+def format_cols(items: List[str], header: Optional[str]) -> str:
+ if header:
+ text = f'{header}:\n'
+ else:
+ text = ''
+
+ nr_items = len(items)
+ if nr_items <= 5:
+ col = 1
+ elif nr_items <= 10:
+ col = 2
+ elif nr_items <= 15:
+ col = 3
+ else:
+ col = 4
+
+ text += FormattedOutput.as_columns(items, col)
+ return text
diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py
index 1aecc1cd..7f9b9fd6 100644
--- a/archinstall/scripts/guided.py
+++ b/archinstall/scripts/guided.py
@@ -5,6 +5,7 @@ from typing import Any, TYPE_CHECKING
import archinstall
from archinstall import info, debug
from archinstall import SysInfo
+from archinstall.lib import locale
from archinstall.lib import disk
from archinstall.lib.global_menu import GlobalMenu
from archinstall.default_profiles.applications.pipewire import PipewireProfile
@@ -42,14 +43,10 @@ def ask_user_questions():
global_menu.enable('archinstall-language')
- global_menu.enable('keyboard-layout')
-
# Set which region to download packages from during the installation
global_menu.enable('mirror_config')
- global_menu.enable('sys-language')
-
- global_menu.enable('sys-encoding')
+ global_menu.enable('locale_config')
global_menu.enable('disk_config', mandatory=True)
@@ -76,7 +73,7 @@ def ask_user_questions():
global_menu.enable('audio')
# Ask for preferred kernel:
- global_menu.enable('kernels')
+ global_menu.enable('kernels', mandatory=True)
global_menu.enable('packages')
@@ -114,9 +111,7 @@ def perform_installation(mountpoint: Path):
# Retrieve list of additional repositories and set boolean values appropriately
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
-
- locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}"
-
+ locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
with Installer(
@@ -147,7 +142,7 @@ def perform_installation(mountpoint: Path):
testing=enable_testing,
multilib=enable_multilib,
hostname=archinstall.arguments.get('hostname', 'archlinux'),
- locales=[locale]
+ locale_config=locale_config
)
if mirror_config := archinstall.arguments.get('mirror_config', None):
@@ -210,7 +205,7 @@ def perform_installation(mountpoint: Path):
# This step must be after profile installs to allow profiles_bck to install language pre-requisits.
# After which, this step will set the language both for console and x11 if x11 was installed for instance.
- installation.set_keyboard_language(archinstall.arguments['keyboard-layout'])
+ installation.set_keyboard_language(locale_config.kb_layout)
if profile_config := archinstall.arguments.get('profile_config', None):
profile_config.profile.post_install(installation)
diff --git a/archinstall/scripts/swiss.py b/archinstall/scripts/swiss.py
index 1998f073..375458a1 100644
--- a/archinstall/scripts/swiss.py
+++ b/archinstall/scripts/swiss.py
@@ -8,6 +8,7 @@ from archinstall import SysInfo, info, debug
from archinstall.lib import mirrors
from archinstall.lib import models
from archinstall.lib import disk
+from archinstall.lib import locale
from archinstall.lib.networking import check_mirror_reachable
from archinstall.lib.profile.profiles_handler import profile_handler
from archinstall.lib import menu
@@ -92,14 +93,14 @@ class SwissMainMenu(GlobalMenu):
match self._execution_mode:
case ExecutionMode.Full | ExecutionMode.Lineal:
options_list = [
- 'keyboard-layout', 'mirror_config', 'disk_config',
+ 'mirror_config', 'disk_config',
'disk_encryption', 'swap', 'bootloader', 'hostname', '!root-password',
'!users', 'profile_config', 'audio', 'kernels', 'packages', 'additional-repositories', 'nic',
'timezone', 'ntp'
]
if archinstall.arguments.get('advanced', False):
- options_list.extend(['sys-language', 'sys-encoding'])
+ options_list.extend(['locale_config'])
mandatory_list = ['disk_config', 'bootloader', 'hostname']
case ExecutionMode.Only_HD:
@@ -107,7 +108,7 @@ class SwissMainMenu(GlobalMenu):
mandatory_list = ['disk_config']
case ExecutionMode.Only_OS:
options_list = [
- 'keyboard-layout', 'mirror_config','bootloader', 'hostname',
+ 'mirror_config','bootloader', 'hostname',
'!root-password', '!users', 'profile_config', 'audio', 'kernels',
'packages', 'additional-repositories', 'nic', 'timezone', 'ntp'
]
@@ -115,7 +116,7 @@ class SwissMainMenu(GlobalMenu):
mandatory_list = ['hostname']
if archinstall.arguments.get('advanced', False):
- options_list += ['sys-language','sys-encoding']
+ options_list += ['locale_config']
case ExecutionMode.Minimal:
pass
case _:
@@ -176,8 +177,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode):
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
-
- locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}"
+ locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
with Installer(
mountpoint,
@@ -206,7 +206,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode):
testing=enable_testing,
multilib=enable_multilib,
hostname=archinstall.arguments.get('hostname', 'archlinux'),
- locales=[locale]
+ locale_config=locale_config
)
if mirror_config := archinstall.arguments.get('mirror_config', None):
@@ -263,7 +263,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode):
# This step must be after profile installs to allow profiles_bck to install language pre-requisits.
# After which, this step will set the language both for console and x11 if x11 was installed for instance.
- installation.set_keyboard_language(archinstall.arguments['keyboard-layout'])
+ installation.set_keyboard_language(locale_config.kb_layout)
if profile_config := archinstall.arguments.get('profile_config', None):
profile_config.profile.post_install(installation)
diff --git a/examples/interactive_installation.py b/examples/interactive_installation.py
index 7c4ffed7..ce1a80ec 100644
--- a/examples/interactive_installation.py
+++ b/examples/interactive_installation.py
@@ -10,6 +10,7 @@ from archinstall.default_profiles.applications.pipewire import PipewireProfile
from archinstall import disk
from archinstall import menu
from archinstall import models
+from archinstall import locale
from archinstall import info, debug
if TYPE_CHECKING:
@@ -21,14 +22,10 @@ def ask_user_questions():
global_menu.enable('archinstall-language')
- global_menu.enable('keyboard-layout')
-
# Set which region to download packages from during the installation
global_menu.enable('mirror_config')
- global_menu.enable('sys-language')
-
- global_menu.enable('sys-encoding')
+ global_menu.enable('locale_config')
global_menu.enable('disk_config', mandatory=True)
@@ -55,7 +52,7 @@ def ask_user_questions():
global_menu.enable('audio')
# Ask for preferred kernel:
- global_menu.enable('kernels')
+ global_menu.enable('kernels', mandatory=True)
global_menu.enable('packages')
@@ -93,9 +90,7 @@ def perform_installation(mountpoint: Path):
# Retrieve list of additional repositories and set boolean values appropriately
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
-
- locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}"
-
+ locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
with Installer(
@@ -126,7 +121,7 @@ def perform_installation(mountpoint: Path):
testing=enable_testing,
multilib=enable_multilib,
hostname=archinstall.arguments.get('hostname', 'archlinux'),
- locales=[locale]
+ locale_config=locale_config
)
if mirror_config := archinstall.arguments.get('mirror_config', None):
@@ -189,7 +184,7 @@ def perform_installation(mountpoint: Path):
# This step must be after profile installs to allow profiles_bck to install language pre-requisits.
# After which, this step will set the language both for console and x11 if x11 was installed for instance.
- installation.set_keyboard_language(archinstall.arguments['keyboard-layout'])
+ installation.set_keyboard_language(locale_config.kb_layout)
if profile_config := archinstall.arguments.get('profile_config', None):
profile_config.profile.post_install(installation)