pmbootstrap-meow/pmb/config/file.py
2024-10-30 12:39:45 +01:00

107 lines
3.9 KiB
Python

# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from pathlib import Path, PosixPath
from pmb.helpers import logging
import configparser
import os
from pmb.core import Config
def load(path: Path) -> Config:
config = Config()
cfg = configparser.ConfigParser()
if os.path.isfile(path):
cfg.read(path)
if "pmbootstrap" not in cfg:
cfg["pmbootstrap"] = {}
if "providers" not in cfg:
cfg["providers"] = {}
for key in Config.__dict__.keys():
if key == "providers":
setattr(config, key, cfg["providers"])
if key == "mirrors" and key in cfg:
for subkey in Config.mirrors.keys():
if subkey in cfg["mirrors"]:
setattr(config, f"mirrors.{subkey}", cfg["mirrors"][subkey])
# default values won't be set in the config file
if key not in cfg["pmbootstrap"]:
continue
# Convert strings to paths
elif type(getattr(Config, key)) is PosixPath:
setattr(config, key, Path(cfg["pmbootstrap"][key]))
# Yeah this really sucks and there isn't a better way to do it without external
# libraries
elif isinstance(getattr(Config, key), list) and isinstance(
getattr(Config, key)[0], PosixPath
):
value = cfg["pmbootstrap"][key]
if not value:
setattr(config, key, value)
else:
setattr(config, key, [Path(p) for p in value.split(",")])
elif isinstance(getattr(Config, key), bool):
setattr(config, key, cfg["pmbootstrap"][key].lower() == "true")
elif key in cfg["pmbootstrap"]:
setattr(config, key, cfg["pmbootstrap"][key])
return config
def serialize(config: Config, skip_defaults: bool = True) -> configparser.ConfigParser:
"""Serialize the config object into a ConfigParser to write it out
in the pmbootstrap_v3.cfg INI format.
:param config: The config object to serialize
:param skip_defaults: Skip writing out default values
"""
cfg = configparser.ConfigParser()
cfg["pmbootstrap"] = {}
cfg["providers"] = {}
cfg["mirrors"] = {}
# .keys() flat maps dictionaries like config.mirrors with
# dotted notation
for key in Config.keys():
# If the default value hasn't changed then don't write out,
# this makes it possible to update the default, otherwise
# we wouldn't be able to tell if the user overwrote it.
if skip_defaults and Config.get_default(key) == getattr(config, key):
continue
if key == "providers":
cfg["providers"] = config.providers
elif key.startswith("mirrors."):
_key = key.split(".")[1]
cfg["mirrors"][_key] = getattr(config, key)
# Convert strings to paths
elif type(getattr(Config, key)) is PosixPath:
cfg["pmbootstrap"][key] = str(getattr(config, key))
elif isinstance(getattr(Config, key), list) and isinstance(
getattr(Config, key)[0], PosixPath
):
cfg["pmbootstrap"][key] = ",".join(os.fspath(p) for p in getattr(config, key))
elif isinstance(getattr(Config, key), bool):
cfg["pmbootstrap"][key] = str(getattr(config, key))
else:
cfg["pmbootstrap"][key] = str(getattr(config, key))
return cfg
# FIXME: we should have distinct Config and ConfigFile types
def save(output: Path, config: Config) -> None:
"""Save the config object to the specified path.
IMPORTANT: The global config (available via get_context().config)
has invocation arguments merged into it. Do NOT call save() with
the global config object."""
logging.debug(f"Save config: {output}")
output.parent.mkdir(parents=True, exist_ok=True)
output.touch(0o700, exist_ok=True)
cfg = serialize(config)
with output.open("w") as handle:
cfg.write(handle)