feat(config): Config loading and merging

This commit is contained in:
2026-05-03 15:09:05 +02:00
parent 190fb86758
commit 7b1dfe6ebc
9 changed files with 176 additions and 37 deletions
+58
View File
@@ -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/<repo name>
- url: git@git.janishutz.com:janishutz/nvim
clone_path: ~/projects/ # Project location will be clone_path/<repo name>
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)"
+3 -1
View File
@@ -1,7 +1,8 @@
# yaml-language-server: $schema=config.schema.json # yaml-language-server: $schema=config.schema.json
# TODO: Change the above to an import URL instead # TODO: Change the above to an import URL instead
requires: 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: pkgs:
individual: individual:
@@ -21,6 +22,7 @@ users:
home_dir: True home_dir: True
boot: boot:
managed: True
bootloader: grub bootloader: grub
esp_dir: /boot/ esp_dir: /boot/
theme_folder: ~/.path/to/theme/ theme_folder: ~/.path/to/theme/
+49 -2
View File
@@ -12,20 +12,67 @@ def _load_config_file(file: str):
return parsed 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: def load_config(file: str) -> ArchMgrConfig:
# Load and validate initial config # Load and validate initial config
try: try:
loaded_conf = _load_config_file(file) loaded_conf = _load_config_file(file)
except Exception: except Exception:
return {} return default_config()
if not validator.validate(loaded_conf): if not validator.validate(loaded_conf):
return {} return default_config()
configuration = cast(dict[str, Any], loaded_conf) configuration = cast(dict[str, Any], loaded_conf)
requires = cast(list[str], configuration.pop("requires")) requires = cast(list[str], configuration.pop("requires"))
conf = cast(ArchMgrConfig, configuration) conf = cast(ArchMgrConfig, configuration)
# Recursively load files # Recursively load files
print("Requires", requires)
for conf_file in requires: for conf_file in requires:
conf = merge_configs(conf, load_config(conf_file)) conf = merge_configs(conf, load_config(conf_file))
+14 -8
View File
@@ -1,17 +1,23 @@
from typing import TypedDict from typing import Optional, TypedDict
from config.dtype.cmds import ArchMgrCmdsConfig from config.dtype.cmds import ArchMgrCmdsConfig
from config.dtype.git import ArchMgrGitConfig 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 from config.dtype.pkgs import ArchMgrPkgConfig
class ArchMgrConfig(TypedDict): class ArchMgrConfig(TypedDict):
pkgs: ArchMgrPkgConfig pkgs: ArchMgrPkgConfig
users: list[ArchMgrUserConfig] users: Optional[list[ArchMgrUserConfig]]
boot: ArchMgrBootConfig boot: ArchMgrBootConfig
themes: ArchMgrThemeConfig themes: Optional[ArchMgrThemeConfig]
git: ArchMgrGitConfig git: Optional[ArchMgrGitConfig]
template_data: list[ArchMgrTemplateData] template_data: Optional[list[ArchMgrTemplateData]]
symlinks: list[ArchMgrSymlinkConfig] symlinks: Optional[list[ArchMgrSymlinkConfig]]
cmds: ArchMgrCmdsConfig cmds: Optional[ArchMgrCmdsConfig]
+2 -2
View File
@@ -2,8 +2,8 @@ from typing import Optional, TypedDict
class ArchMgrCmdsConfig(TypedDict): class ArchMgrCmdsConfig(TypedDict):
always: list[ArchMgrCommand] always: Optional[list[ArchMgrCommand]]
once: list[ArchMgrCommand] once: Optional[list[ArchMgrCommand]]
class ArchMgrCommand(TypedDict): class ArchMgrCommand(TypedDict):
+4 -4
View File
@@ -1,13 +1,13 @@
from typing import TypedDict from typing import Optional, TypedDict
class ArchMgrGitConfig(TypedDict): class ArchMgrGitConfig(TypedDict):
creds: ArchMgrGitCredsConfig creds: Optional[ArchMgrGitCredsConfig]
repos: list[ArchMgrGitRepoConfig] repos: Optional[list[ArchMgrGitRepoConfig]]
class ArchMgrGitCredsConfig(TypedDict): class ArchMgrGitCredsConfig(TypedDict):
manager: str manager: Optional[str]
class ArchMgrGitRepoConfig(TypedDict): class ArchMgrGitRepoConfig(TypedDict):
+13 -13
View File
@@ -1,27 +1,27 @@
from typing import TypedDict from typing import Optional, TypedDict
class ArchMgrUserConfig(TypedDict): class ArchMgrUserConfig(TypedDict):
username: str username: str
groups: list[str] groups: Optional[list[str]]
home_dir: bool home_dir: Optional[bool]
sudo_user: bool sudo_user: Optional[bool]
class ArchMgrBootConfig(TypedDict): class ArchMgrBootConfig(TypedDict):
managed: bool managed: bool
bootloader: str bootloader: Optional[str]
esp_dir: str esp_dir: Optional[str]
theme_folder: str theme_folder: Optional[str]
os_prober: bool os_prober: Optional[bool]
class ArchMgrThemeConfig(TypedDict): class ArchMgrThemeConfig(TypedDict):
gtk: str gtk: Optional[str]
qt: str qt: Optional[str]
font: str font: Optional[str]
icon_theme: str icon_theme: Optional[str]
cursor_theme: str cursor_theme: Optional[str]
class ArchMgrTemplateData(TypedDict): class ArchMgrTemplateData(TypedDict):
+5 -5
View File
@@ -2,14 +2,14 @@ from typing import Optional, TypedDict
class ArchMgrPkgConfig(TypedDict): class ArchMgrPkgConfig(TypedDict):
individual: list[str] individual: Optional[list[str]]
repos: ArchMgrReposConfig repos: Optional[ArchMgrReposConfig]
bundles: list[ArchMgrBundleConfig] bundles: Optional[list[ArchMgrBundleConfig]]
class ArchMgrReposConfig(TypedDict): class ArchMgrReposConfig(TypedDict):
enabled_repos: list[ArchMgrRepoSettings] enabled_repos: Optional[list[ArchMgrRepoSettings]]
reflector: ArchMgrReflectorConfig reflector: Optional[ArchMgrReflectorConfig]
class ArchMgrReflectorConfig(TypedDict): class ArchMgrReflectorConfig(TypedDict):
+28 -2
View File
@@ -1,3 +1,4 @@
from typing import cast
from config.dtype import ArchMgrConfig 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: if len(new_config) == 0 or len(config) == 0:
return config return config
# Merge configs def combine(a: dict, b: dict):
return config 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)))