From 7b1dfe6ebc8084a1417d6d62e8ed38cd53d298b3 Mon Sep 17 00:00:00 2001 From: Janis Hutz Date: Sun, 3 May 2026 15:09:05 +0200 Subject: [PATCH] feat(config): Config loading and merging --- config-include-test.yml | 58 ++++++++++++++++++++++++++++++++++++++++ config.yml | 4 ++- config/__init__.py | 51 +++++++++++++++++++++++++++++++++-- config/dtype/__init__.py | 22 +++++++++------ config/dtype/cmds.py | 4 +-- config/dtype/git.py | 8 +++--- config/dtype/others.py | 26 +++++++++--------- config/dtype/pkgs.py | 10 +++---- config/merger.py | 30 +++++++++++++++++++-- 9 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 config-include-test.yml diff --git a/config-include-test.yml b/config-include-test.yml new file mode 100644 index 0000000..86ea060 --- /dev/null +++ b/config-include-test.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=config.schema.json +# TODO: Change the above to an import URL instead +requires: + - path/to/other/configs/relative/to/this # Reads the other configs after finishing this one + +pkgs: + individual: + - pkg_name + bundles: + - name: hyprland + repos: + reflector: + enabled: false + countries: + - Switzerland + +users: + - username: username + groups: + - group_1 + home_dir: True + +boot: + managed: True + bootloader: grub + esp_dir: /boot/ + theme_folder: ~/.path/to/theme/ + os_prober: False + # Also copies over the /etc/default/grub config or equivalent for other supported bootloaders + + # TODO: Desktops, login managers, full disk encryption etc configuration? + +themes: + gtk: theme_name + qt: theme_name # or use_gtk to use the gtk theme instaed + font: Comfortaa 11 # the font name to be used (also needs to be installed) + icon_theme: candy-icons # The icon theme to use (also needs to be installed) + cursor_theme: oreo_spark_blue_cursors # TODO: Consider if GTK settings file should just be copied + +git: + creds: + manager: git-credential-manager # or none + repos: + - url: https://github.com/janishutz/janishutz + clone_path: ~/projects/ # Project location will be clone_path/ + - url: git@git.janishutz.com:janishutz/nvim + clone_path: ~/projects/ # Project location will be clone_path/ + +template_data: + - name: template_data_name + data: the_data + +cmds: + always: + - cmd: "cmd to run every time archmgr is run" + once: + - name: "cmd1" + cmd: "cmd to run on first execution of archmgr (or if not executed previously)" diff --git a/config.yml b/config.yml index c62fb6e..6adf582 100644 --- a/config.yml +++ b/config.yml @@ -1,7 +1,8 @@ # yaml-language-server: $schema=config.schema.json # TODO: Change the above to an import URL instead requires: - - path/to/other/configs/relative/to/this # Reads the other configs after finishing this one + - ./config-include-test.yml # Reads the other configs after finishing this one + # - path/to/other/configs/relative/to/this # Reads the other configs after finishing this one pkgs: individual: @@ -21,6 +22,7 @@ users: home_dir: True boot: + managed: True bootloader: grub esp_dir: /boot/ theme_folder: ~/.path/to/theme/ diff --git a/config/__init__.py b/config/__init__.py index e52bb46..b6b5ed6 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -12,20 +12,67 @@ def _load_config_file(file: str): return parsed +def default_config() -> ArchMgrConfig: + return { + "pkgs": { + "individual": [], + "bundles": [], + "repos": { + "enabled_repos": [ + { + "name": "core", + "setup_cmds": [], + "mirrors": {"use_default": True, "extra_mirrors": []}, + } + ], + "reflector": { + "enabled": False, + "count": 0, + "countries": [], + "interval": 1, + }, + }, + }, + "boot": { + "managed": False, + "bootloader": "grub", + "esp_dir": "/boot", + "os_prober": False, + "theme_folder": "/usr/share/themes/grub", + }, + "cmds": {"always": [], "once": []}, + "git": { + "repos": [], + "creds": {"manager": "git-credential-manager"}, + }, + "users": [], + "symlinks": [], + "template_data": [], + "themes": { + "cursor_theme": "oreo_spark_blue_cursor", + "font": "Comfortaa 11", + "gtk": "Adaptive-Theme", + "qt": "gtk3", + "icon_theme": "candy-icons" +}, + } + + def load_config(file: str) -> ArchMgrConfig: # Load and validate initial config try: loaded_conf = _load_config_file(file) except Exception: - return {} + return default_config() if not validator.validate(loaded_conf): - return {} + return default_config() configuration = cast(dict[str, Any], loaded_conf) requires = cast(list[str], configuration.pop("requires")) conf = cast(ArchMgrConfig, configuration) # Recursively load files + print("Requires", requires) for conf_file in requires: conf = merge_configs(conf, load_config(conf_file)) diff --git a/config/dtype/__init__.py b/config/dtype/__init__.py index dd4f0de..4ba8f69 100644 --- a/config/dtype/__init__.py +++ b/config/dtype/__init__.py @@ -1,17 +1,23 @@ -from typing import TypedDict +from typing import Optional, TypedDict from config.dtype.cmds import ArchMgrCmdsConfig from config.dtype.git import ArchMgrGitConfig -from config.dtype.others import ArchMgrBootConfig, ArchMgrSymlinkConfig, ArchMgrTemplateData, ArchMgrThemeConfig, ArchMgrUserConfig +from config.dtype.others import ( + ArchMgrBootConfig, + ArchMgrSymlinkConfig, + ArchMgrTemplateData, + ArchMgrThemeConfig, + ArchMgrUserConfig, +) from config.dtype.pkgs import ArchMgrPkgConfig class ArchMgrConfig(TypedDict): pkgs: ArchMgrPkgConfig - users: list[ArchMgrUserConfig] + users: Optional[list[ArchMgrUserConfig]] boot: ArchMgrBootConfig - themes: ArchMgrThemeConfig - git: ArchMgrGitConfig - template_data: list[ArchMgrTemplateData] - symlinks: list[ArchMgrSymlinkConfig] - cmds: ArchMgrCmdsConfig + themes: Optional[ArchMgrThemeConfig] + git: Optional[ArchMgrGitConfig] + template_data: Optional[list[ArchMgrTemplateData]] + symlinks: Optional[list[ArchMgrSymlinkConfig]] + cmds: Optional[ArchMgrCmdsConfig] diff --git a/config/dtype/cmds.py b/config/dtype/cmds.py index 9749b0a..2216f87 100644 --- a/config/dtype/cmds.py +++ b/config/dtype/cmds.py @@ -2,8 +2,8 @@ from typing import Optional, TypedDict class ArchMgrCmdsConfig(TypedDict): - always: list[ArchMgrCommand] - once: list[ArchMgrCommand] + always: Optional[list[ArchMgrCommand]] + once: Optional[list[ArchMgrCommand]] class ArchMgrCommand(TypedDict): diff --git a/config/dtype/git.py b/config/dtype/git.py index 6bfc63a..66f2a14 100644 --- a/config/dtype/git.py +++ b/config/dtype/git.py @@ -1,13 +1,13 @@ -from typing import TypedDict +from typing import Optional, TypedDict class ArchMgrGitConfig(TypedDict): - creds: ArchMgrGitCredsConfig - repos: list[ArchMgrGitRepoConfig] + creds: Optional[ArchMgrGitCredsConfig] + repos: Optional[list[ArchMgrGitRepoConfig]] class ArchMgrGitCredsConfig(TypedDict): - manager: str + manager: Optional[str] class ArchMgrGitRepoConfig(TypedDict): diff --git a/config/dtype/others.py b/config/dtype/others.py index 6d50790..63fd4f3 100644 --- a/config/dtype/others.py +++ b/config/dtype/others.py @@ -1,27 +1,27 @@ -from typing import TypedDict +from typing import Optional, TypedDict class ArchMgrUserConfig(TypedDict): username: str - groups: list[str] - home_dir: bool - sudo_user: bool + groups: Optional[list[str]] + home_dir: Optional[bool] + sudo_user: Optional[bool] class ArchMgrBootConfig(TypedDict): managed: bool - bootloader: str - esp_dir: str - theme_folder: str - os_prober: bool + bootloader: Optional[str] + esp_dir: Optional[str] + theme_folder: Optional[str] + os_prober: Optional[bool] class ArchMgrThemeConfig(TypedDict): - gtk: str - qt: str - font: str - icon_theme: str - cursor_theme: str + gtk: Optional[str] + qt: Optional[str] + font: Optional[str] + icon_theme: Optional[str] + cursor_theme: Optional[str] class ArchMgrTemplateData(TypedDict): diff --git a/config/dtype/pkgs.py b/config/dtype/pkgs.py index 66abd02..d1ff1e9 100644 --- a/config/dtype/pkgs.py +++ b/config/dtype/pkgs.py @@ -2,14 +2,14 @@ from typing import Optional, TypedDict class ArchMgrPkgConfig(TypedDict): - individual: list[str] - repos: ArchMgrReposConfig - bundles: list[ArchMgrBundleConfig] + individual: Optional[list[str]] + repos: Optional[ArchMgrReposConfig] + bundles: Optional[list[ArchMgrBundleConfig]] class ArchMgrReposConfig(TypedDict): - enabled_repos: list[ArchMgrRepoSettings] - reflector: ArchMgrReflectorConfig + enabled_repos: Optional[list[ArchMgrRepoSettings]] + reflector: Optional[ArchMgrReflectorConfig] class ArchMgrReflectorConfig(TypedDict): diff --git a/config/merger.py b/config/merger.py index 829da5e..9271768 100644 --- a/config/merger.py +++ b/config/merger.py @@ -1,3 +1,4 @@ +from typing import cast from config.dtype import ArchMgrConfig @@ -5,5 +6,30 @@ def merge_configs(config: ArchMgrConfig, new_config: ArchMgrConfig) -> ArchMgrCo if len(new_config) == 0 or len(config) == 0: return config - # Merge configs - return config + def combine(a: dict, b: dict): + combined = {} + for key in b: + val = b[key] + try: + a[key] + if isinstance(val, dict): + combined[key] = combine(val, a[key]) + elif isinstance(val, list): + combined[key] = val + for v in a[key]: + combined[key].append(v) + else: + combined[key] = val + except KeyError: + combined[key] = val + + for key in a: + try: + b[key] + except KeyError: + combined[key] = a[key] + + return combined + + # Merge configs (using nasty casts) + return cast(ArchMgrConfig, combine(cast(dict, config), cast(dict, new_config)))