# 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)