from pathlib import Path import json from typing import Dict, Optional, List import os import logging import toml import random logger = logging.Logger(__name__) PREFIX = "MOMMY" RESPONSES_FILE = Path(__file__).parent / "responses.json" ADDITIONAL_ENV_VARS = { "pronoun": "PRONOUNS", "role": "ROLES", "emote": "EMOTES", "mood": "MOODS", } def _get_xdg_config_dir() -> Path: res = os.environ.get("XDG_CONFIG_HOME") if res is not None: return Path(res) xdg_user_dirs_file = Path(os.environ.get("XDG_CONFIG_HOME") or Path(Path.home(), ".config", "user-dirs.dirs")) xdg_user_dirs_default_file = Path("/etc/xdg/user-dirs.defaults") def get_dir_from_xdg_file(xdg_file_path: Path, key_a: str) -> Optional[str]: if not xdg_file_path.exists(): logger.info("config file not found in %s", str(xdg_file_path)) return with xdg_file_path.open("r") as f: for line in f: if line.startswith("#"): continue parts = line.split("=") if len(parts) > 2: continue key_b = parts[0].lower().strip() value = parts[1].strip().split("#")[0] if key_a.lower() == key_b: return value logger.info("key %s not found in %s", key_a, str(xdg_file_path)) res = get_dir_from_xdg_file(xdg_user_dirs_file, "XDG_CONFIG_HOME") if res is not None: return Path(res) res = get_dir_from_xdg_file(xdg_user_dirs_default_file, "CONFIG") if res is not None: return Path(Path.home(), res) res = get_dir_from_xdg_file(xdg_user_dirs_default_file, "XDG_CONFIG_HOME") if res is not None: return Path(Path.home(), res) default = Path(Path.home(), ".config") logging.info("falling back to %s", default) return default CONFIG_DIRECTORY = _get_xdg_config_dir() / "mommy" CONFIG_FILES = [ CONFIG_DIRECTORY / "python-mommy.toml", CONFIG_DIRECTORY / "mommy.toml", ] COMPILED_CONFIG_FILE = CONFIG_DIRECTORY / "responses.json" def _load_config_file(config_file: Path) -> Optional[Dict[str, List[str]]]: global CONFIG if not config_file.exists(): return None with config_file.open("r") as f: data = toml.load(f) result = {} for key, value in data.items(): if isinstance(value, str): result[key] = [value] else: result[key] = value return result ADDITIONAL_PROGRAM_PREFIXES = [ "cargo", # only as fallback if user already configured cargo ] def _get_env_var_names(name: str): BASE = PREFIX + "_" + name.upper() yield "PYTHON_" + BASE yield BASE for a in ADDITIONAL_PROGRAM_PREFIXES: yield a + "_" + BASE def _get_env_value(name: str) -> Optional[str]: if name in ADDITIONAL_ENV_VARS: for key in _get_env_var_names(ADDITIONAL_ENV_VARS[name]): val = os.environ.get(key) if val is not None: return val for key in _get_env_var_names(name): val = os.environ.get(key) if val is not None: return val def compile_config(): global RESPONSES_FILE data = json.loads(RESPONSES_FILE.read_text()) config_definition: Dict[str, dict] = data["vars"] mood_definitions: Dict[str, dict] = data["moods"] # environment variables for compatibility with cargo mommy # fill ADDITIONAL_ENV_VARS with the "env_key" values for key, conf in config_definition.items(): if "env_key" in conf: ADDITIONAL_ENV_VARS[key] = conf["env_key"] # set config to the default values config: Dict[str, List[str]] = {} for key, conf in config_definition.items(): config[key] = conf["defaults"] # load config file config_file_data: Optional[Dict[str, List[str]]] for c in CONFIG_FILES: config_file_data = _load_config_file(c) if config_file_data is not None: break if config_file_data is not None: config.update(config_file_data) # fill config with env for key, conf in config_definition.items(): val = _get_env_value(key) if val is not None: config[key] = val.split("/") # validate moods for mood in config["mood"]: if mood not in mood_definitions: supported_moods_str = ", ".join(mood_definitions.keys()) print(f"{random.choice(config['role'])} doesn't know how to feel {mood}... {random.choice(config['pronoun'])} moods are {supported_moods_str}") exit(1) # compile compiled = {} compiled_moods = compiled["moods"] = {} compiled_vars = compiled["vars"] = {} for mood in config["mood"]: compiled_moods[mood] = mood_definitions[mood] del config["mood"] compiled_vars.update(config) print("writing compiled config to " + str(COMPILED_CONFIG_FILE)) with COMPILED_CONFIG_FILE.open("w") as f: json.dump(compiled, f, indent=4)