Compare commits
	
		
			10 Commits
		
	
	
		
			ac7360ffe8
			...
			3a7b46219e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3a7b46219e | |||
|  | ad1aff0438 | ||
|  | 3213e8a21f | ||
|  | 94c585fa7a | ||
|  | a8a1f425cc | ||
|  | be95c352d6 | ||
|  | d1fe9d55d5 | ||
|  | 350f583b89 | ||
|  | ad61ad4643 | ||
|  | cc5b7ae55e | 
| @@ -1,7 +1,7 @@ | ||||
| [project] | ||||
| description = "Mommy's here to support you when running python (in a virtual enviroment)~ ❤️" | ||||
| name = "python_mommy_venv" | ||||
| dependencies = ["toml"] | ||||
| dependencies = ["toml", "requests"] | ||||
| authors = [] | ||||
| readme = "README.md" | ||||
| requires-python = ">=3.9" | ||||
| @@ -15,7 +15,6 @@ license-files = ["LICENSE"] | ||||
|  | ||||
| [project.scripts] | ||||
| python-mommy-dev = "python_mommy_venv.__main__:development" | ||||
| python-mommy-generate-config = "python_mommy_venv.__main__:write_current_config" | ||||
| mommify-venv = "python_mommy_venv.__main__:mommify_venv" | ||||
|  | ||||
| [build-system] | ||||
|   | ||||
| @@ -1,21 +1,32 @@ | ||||
| import random | ||||
| import sys | ||||
| from typing import Optional | ||||
| import json | ||||
|  | ||||
| from .config import get_mood, get_template_values | ||||
| from .static import RESPONSES, Situation, colors | ||||
| from .static import colors, get_compiled_config_file | ||||
|  | ||||
|  | ||||
| def get_response(situation: Situation, colorize: Optional[bool] = None): | ||||
| def get_response_from_situation(situation: str, colorize: Optional[bool] = None): | ||||
|     if colorize is None: | ||||
|         colorize = sys.stdout.isatty() | ||||
|  | ||||
|     # get message | ||||
|     mood = get_mood() | ||||
|     template = random.choice(RESPONSES[mood][situation]) | ||||
|     message = template.format(**get_template_values(mood)) | ||||
|     config = json.loads(get_compiled_config_file().read_text()) | ||||
|     existing_moods = list(config["moods"].keys()) | ||||
|     template_options = config["moods"][random.choice(existing_moods)][situation] | ||||
|     template: str = random.choice(template_options) | ||||
|  | ||||
|     template_values = {} | ||||
|     for key, values in config["vars"].items(): | ||||
|         template_values[key] = random.choice(values) | ||||
|  | ||||
|     message = template.format(**template_values) | ||||
|  | ||||
|     # return message | ||||
|     if not colorize: | ||||
|         return message | ||||
|     return colors.BOLD + message + colors.ENDC | ||||
|  | ||||
|  | ||||
| def get_response(code: int, colorize: Optional[bool] = None) -> str: | ||||
|     return get_response_from_situation("positive" if code == 0 else "negative") | ||||
|   | ||||
| @@ -3,16 +3,14 @@ from pathlib import Path | ||||
| import stat | ||||
| import subprocess | ||||
| import logging | ||||
| import json | ||||
| import argparse | ||||
|  | ||||
| import toml | ||||
|  | ||||
| from . import get_response | ||||
| from .static import Situation | ||||
| from .config import CONFIG_FILES, CONFIG_DIRECTORY, generate_current_configuration | ||||
|  | ||||
| from .responses import compile_config | ||||
| from .static import IS_VENV, VENV_DIRECTORY, CONFIG_DIRECTORY, COMPILED_CONFIG_FILE_NAME | ||||
|  | ||||
| logging.basicConfig( | ||||
|     format=' %(message)s', | ||||
|     format='%(message)s', | ||||
|     force=True, | ||||
| ) | ||||
|  | ||||
| @@ -21,7 +19,18 @@ log_level = logging.INFO | ||||
| mommy_logger = logging.getLogger("mommy") | ||||
| mommy_logger.setLevel(logging.INFO) | ||||
| serious_logger = logging.getLogger("serious") | ||||
| serious_logger.setLevel(logging.WARNING) | ||||
| serious_logger.setLevel(50) | ||||
|  | ||||
|  | ||||
| def config_logging(verbose: bool): | ||||
|     if verbose: | ||||
|         logging.basicConfig( | ||||
|             format=logging.BASIC_FORMAT, | ||||
|             force=True, | ||||
|         ) | ||||
|         logging.getLogger().setLevel(logging.DEBUG) | ||||
|         mommy_logger.setLevel(50) | ||||
|         serious_logger.setLevel(logging.DEBUG) | ||||
|  | ||||
|  | ||||
| def development(): | ||||
| @@ -29,28 +38,14 @@ def development(): | ||||
|     if len(sys.argv) > 1: | ||||
|         s = sys.argv[1] | ||||
|  | ||||
|     print(get_response(Situation(s))) | ||||
|     compile_config() | ||||
|      | ||||
|  | ||||
| def write_current_config(): | ||||
|     f = "python-mommy.toml" | ||||
|     if len(sys.argv) > 1: | ||||
|         f = sys.argv[1] | ||||
|  | ||||
|     config_file = CONFIG_DIRECTORY / f | ||||
|     print(f"writing to: {config_file}") | ||||
|  | ||||
|     data = toml.dumps(generate_current_configuration()) | ||||
|     print(data) | ||||
|     with config_file.open("w") as f: | ||||
|         f.write(data) | ||||
|  | ||||
|  | ||||
| WRAPPER_TEMPLATE = """#!{inner_bin} | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| import sys, subprocess | ||||
| from python_mommy_venv import get_response, Situation | ||||
| from python_mommy_venv import get_response | ||||
|  | ||||
|  | ||||
| INTERPRETER = "{inner_bin}" | ||||
| @@ -58,7 +53,7 @@ result = subprocess.run([INTERPRETER] + sys.argv[1:]) | ||||
| code = result.returncode | ||||
|  | ||||
| print() | ||||
| print(get_response(Situation.POSITIVE if code == 0 else Situation.NEGATIVE)) | ||||
| print(get_response(code)) | ||||
| exit(code=code) | ||||
| """ | ||||
|  | ||||
| @@ -98,6 +93,13 @@ PIP_HOOK = """# GENERATED BY MOMMY | ||||
|     sys.exit(code)""" | ||||
|  | ||||
|  | ||||
| def assert_venv(): | ||||
|     if not IS_VENV: | ||||
|         mommy_logger.error("mommy doesn't run in a virtual environment~") | ||||
|         serious_logger.error("this has to run in a virtual environment") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def wrap_interpreter(path: Path): | ||||
|     mommy_logger.info("mommy found a symlink to an interpreter~ %s", str(path)) | ||||
|     serious_logger.info("interpreter symlink found at %s", str(path)) | ||||
| @@ -145,12 +147,39 @@ def install_pip_hook(path: Path): | ||||
|  | ||||
|  | ||||
| def mommify_venv(): | ||||
|     parser = argparse.ArgumentParser(description="patch the virtual environment to use mommy") | ||||
|      | ||||
|     parser.add_argument( | ||||
|         "-v", "--verbose", | ||||
|         action="store_true", | ||||
|         help="enable verbose and serious output" | ||||
|     ) | ||||
|  | ||||
|     v = ".venv" | ||||
|     if len(sys.argv) > 1: | ||||
|         v = sys.argv[1] | ||||
|     parser.add_argument( | ||||
|         "-l", "--local", | ||||
|         action="store_true", | ||||
|         help="compile the config only for the current virtual environment" | ||||
|     ) | ||||
|  | ||||
|     bin_path = Path(v, "bin") | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     config_logging(args.verbose) | ||||
|     assert_venv() | ||||
|  | ||||
|     compiled_base_dir = VENV_DIRECTORY if args.local else CONFIG_DIRECTORY | ||||
|     compiled_config_file = compiled_base_dir / COMPILED_CONFIG_FILE_NAME | ||||
|     compiled = compile_config() | ||||
|     mommy_logger.info("mommy writes its moods in %s", compiled_config_file) | ||||
|     serious_logger.info("writing compiled config file to %s", compiled_config_file) | ||||
|     compiled_base_dir.mkdir(parents=True, exist_ok=True) | ||||
|     with compiled_config_file.open("w") as f: | ||||
|         json.dump(compiled, f, indent=4) | ||||
|     if not args.local: | ||||
|         (VENV_DIRECTORY / COMPILED_CONFIG_FILE_NAME).unlink(missing_ok=True) | ||||
|  | ||||
|     mommy_logger.info("") | ||||
|  | ||||
|     bin_path = VENV_DIRECTORY / "bin" | ||||
|     bin_path = bin_path.resolve() | ||||
|  | ||||
|     mommy_logger.info("mommy looks in %s to mess your system up~ <33", str(bin_path)) | ||||
| @@ -162,10 +191,10 @@ def mommify_venv(): | ||||
|         if path.is_symlink(): | ||||
|             # could be python interpreter | ||||
|             # check for both just to be more expressive | ||||
|             if name.startswith("inner_") or not name.startswith("python"): | ||||
|             if name.startswith("inner_"): | ||||
|                 continue | ||||
|              | ||||
|             if subprocess.run([str(path), '-c', '"exit()"']) != 0: | ||||
|             if subprocess.run([str(path), '-c', '"exit(0)"']).returncode != 0: | ||||
|                 continue | ||||
|  | ||||
|             wrap_interpreter(path) | ||||
| @@ -176,6 +205,3 @@ def mommify_venv(): | ||||
|                 continue | ||||
|  | ||||
|             install_pip_hook(path) | ||||
|  | ||||
|         serious_logger.info("") | ||||
|         mommy_logger.info("") | ||||
|   | ||||
| @@ -1,193 +0,0 @@ | ||||
| from typing import Optional, List, Dict, Union | ||||
| import os | ||||
| from os.path import expandvars | ||||
| from sys import platform | ||||
| import logging | ||||
| from pathlib import Path | ||||
| import toml | ||||
| import random | ||||
|  | ||||
| from .static import RESPONSES | ||||
|  | ||||
| logger = logging.Logger("mommy_config") | ||||
|  | ||||
| PREFIXES = [ | ||||
|     "PYTHON", # first one is always the prefix of the current program | ||||
|     "CARGO", | ||||
| ] | ||||
|  | ||||
|  | ||||
| # env key is just a backup key for compatibility with cargo mommy | ||||
| CONFIG = { | ||||
|     "mood": { | ||||
|         "defaults": ["chill"] | ||||
|     }, | ||||
|     "emote": { | ||||
|         "defaults": ["❤️", "💖", "💗", "💓", "💞"] | ||||
|     }, | ||||
|     "pronoun": { | ||||
|         "defaults": ["her"] | ||||
|     }, | ||||
|     "role": { | ||||
|         "defaults": ["mommy"] | ||||
|     }, | ||||
|     "affectionate_term": { | ||||
|         "defaults": ["girl"], | ||||
|         "env_key": "LITTLE" | ||||
|     }, | ||||
|     "denigrating_term": { | ||||
|         "spiciness": "yikes", | ||||
|         "defaults": ["slut", "toy", "pet", "pervert", "whore"], | ||||
|         "env_key": "FUCKING" | ||||
|     }, | ||||
|     "part": { | ||||
|         "spiciness": "yikes", | ||||
|         "defaults": ["milk"] | ||||
|     } | ||||
| } | ||||
|  | ||||
| MOOD_PRIORITIES: Dict[str, int] = {} | ||||
| for i, mood in enumerate(RESPONSES): | ||||
|     MOOD_PRIORITIES[mood] = i | ||||
|  | ||||
| PREFIXES = [ | ||||
|     "PYTHON", # first one is always the prefix of the current program | ||||
|     "CARGO", | ||||
| ] | ||||
|  | ||||
| for key, value in CONFIG.items(): | ||||
|     env_keys = [ | ||||
|         PREFIXES[0] + "_MOMMY_" + key.upper(), | ||||
|         "MOMMY_" + key.upper(), | ||||
|         *(p + "_MOMMY_" + key.upper() for p in PREFIXES) | ||||
|     ] | ||||
|  | ||||
|     if value.get("env_key") is not None: | ||||
|         env_keys.append(value.get("env_key")) | ||||
|  | ||||
|     for env_key in env_keys: | ||||
|         res = os.environ.get(env_key) | ||||
|         if res is not None: | ||||
|             value["default"] = res.split("/") | ||||
|  | ||||
|  | ||||
| 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", | ||||
| ] | ||||
|  | ||||
| def load_config_file(config_file: Path) -> bool: | ||||
|     global CONFIG | ||||
|     if not config_file.exists(): | ||||
|         return False | ||||
|  | ||||
|     with config_file.open("r") as f: | ||||
|         data = toml.load(f) | ||||
|          | ||||
|         for key, value in data.items(): | ||||
|             if isinstance(value, str): | ||||
|                 CONFIG[key]["defaults"] = [value] | ||||
|             else: | ||||
|                 CONFIG[key]["defaults"] = value | ||||
|  | ||||
|     return True | ||||
|  | ||||
|  | ||||
| for c in CONFIG_FILES: | ||||
|     if load_config_file(c): | ||||
|         break | ||||
|  | ||||
|  | ||||
| # validate config file | ||||
| if True: | ||||
|     unfiltered_moods = CONFIG["mood"]["defaults"] | ||||
|     CONFIG["mood"]["defaults"] = filtered_moods = [] | ||||
|     for mood in unfiltered_moods: | ||||
|         if mood in RESPONSES: | ||||
|             filtered_moods.append(mood) | ||||
|         else: | ||||
|             logger.warning("mood %s isn't supported", mood) | ||||
|  | ||||
|  | ||||
| def get_mood() -> str: | ||||
|     return random.choice(CONFIG["mood"]["defaults"])  | ||||
|  | ||||
| def get_template_values(mood: str) -> Dict[str, str]: | ||||
|     mood_spice_level = MOOD_PRIORITIES[mood] | ||||
|     result = {} | ||||
|      | ||||
|     for key, value in CONFIG.items(): | ||||
|         spice = value.get("spiciness") | ||||
|         allow_key = spice is None | ||||
|         if not allow_key: | ||||
|             key_spice_level = MOOD_PRIORITIES[spice] | ||||
|             allow_key = mood_spice_level >= key_spice_level | ||||
|          | ||||
|         if not allow_key: | ||||
|             continue | ||||
|  | ||||
|         result[key] = random.choice(value["defaults"]) | ||||
|  | ||||
|     return result | ||||
|  | ||||
| def generate_current_configuration() -> Dict[str, Union[str, List[str]]]: | ||||
|     global CONFIG | ||||
|     generated = {} | ||||
|  | ||||
|     for key, definition in CONFIG.items(): | ||||
|         value = definition["defaults"] | ||||
|         if len(value) == 1: | ||||
|             value = value[0] | ||||
|  | ||||
|         generated[key] = value | ||||
|  | ||||
|     return generated | ||||
							
								
								
									
										130
									
								
								python_mommy_venv/responses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								python_mommy_venv/responses.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| from pathlib import Path | ||||
| import json | ||||
| from typing import Dict, Optional, List | ||||
| import os | ||||
| import logging | ||||
| import toml | ||||
| import random | ||||
| import requests | ||||
|  | ||||
| from .static import get_config_file | ||||
|  | ||||
| mommy_logger = logging.getLogger("mommy") | ||||
| serious_logger = logging.getLogger("serious") | ||||
|  | ||||
| PREFIX = "MOMMY" | ||||
|  | ||||
| RESPONSES_URL = "https://raw.githubusercontent.com/diamondburned/go-mommy/refs/heads/main/responses.json" | ||||
| RESPONSES_FILE = Path(__file__).parent / "responses.json" | ||||
| ADDITIONAL_ENV_VARS = { | ||||
|     "pronoun": "PRONOUNS", | ||||
|     "role": "ROLES", | ||||
|     "emote": "EMOTES", | ||||
|     "mood": "MOODS", | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| def _load_config_file(config_file: Path) -> Dict[str, List[str]]: | ||||
|     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(disable_requests: bool = False) -> dict: | ||||
|     global RESPONSES_FILE, RESPONSES_URL | ||||
|  | ||||
|     data = json.loads(RESPONSES_FILE.read_text()) | ||||
|      | ||||
|     if not disable_requests: | ||||
|         mommy_logger.info("mommy downloads newest responses for her girl~ %s", RESPONSES_URL) | ||||
|         serious_logger.info("downloading cargo mommy responses: %s", RESPONSES_URL) | ||||
|         r = requests.get(RESPONSES_URL) | ||||
|         data = r.json() | ||||
|  | ||||
|     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 = get_config_file() | ||||
|     if config_file is not None: | ||||
|         config.update(_load_config_file(config_file)) | ||||
|  | ||||
|     # 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()) | ||||
|             mommy_logger.error( | ||||
|                 "%s doesn't know how to feel %s... %s moods are %s", | ||||
|                 random.choice(config['role']), | ||||
|                 mood, | ||||
|                 random.choice(config['pronoun']), | ||||
|                 supported_moods_str, | ||||
|             ) | ||||
|             serious_logger.error( | ||||
|                 "mood '%s' doesn't exist. moods are %s", | ||||
|                 mood, | ||||
|                 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) | ||||
|  | ||||
|     return compiled | ||||
| @@ -1,136 +1,14 @@ | ||||
| from __future__ import annotations | ||||
| from enum import Enum | ||||
|  | ||||
| class Situation(Enum): | ||||
|     POSITIVE = "positive" | ||||
|     NEGATIVE = "negative" | ||||
|     OVERFLOW = "overflow " | ||||
| from pathlib import Path | ||||
| import os | ||||
| import logging | ||||
| from typing import Optional | ||||
| import sys | ||||
|  | ||||
|  | ||||
| RESPONSES = { | ||||
|     "chill": { | ||||
|         Situation.POSITIVE: [ | ||||
|             "*pets your head*", | ||||
|             "*gives you scritches*", | ||||
|             "you're such a smart cookie~", | ||||
|             "that's a good {affectionate_term}~", | ||||
|             "{role} thinks {pronoun} little {affectionate_term} earned a big hug~", | ||||
|             "good {affectionate_term}~\n{role}'s so proud of you~", | ||||
|             "aww, what a good {affectionate_term}~\n{role} knew you could do it~", | ||||
|             "you did it~!", | ||||
|             "{role} loves you~", | ||||
|             "*gives you a sticker*", | ||||
|             "*boops your nose*", | ||||
|             "*wraps you in a big hug*", | ||||
|             "well done~!\n{role} is so happy for you~", | ||||
|             "what a good {affectionate_term} you are~", | ||||
|             "that's {role}'s clever little {affectionate_term}~", | ||||
|             "you're doing so well~!", | ||||
|             "you're making {role} so happy~", | ||||
|             "{role} loves {pronoun} cute little {affectionate_term}~" | ||||
|         ], | ||||
|         Situation.NEGATIVE: [ | ||||
|             "{role} believes in you~", | ||||
|             "don't forget to hydrate~", | ||||
|             "aww, you'll get it next time~", | ||||
|             "do you need {role}'s help~?", | ||||
|             "everything's gonna be ok~", | ||||
|             "{role} still loves you no matter what~", | ||||
|             "oh no did {role}'s little {affectionate_term} make a big mess~?", | ||||
|             "{role} knows {pronoun} little {affectionate_term} can do better~", | ||||
|             "{role} still loves you~", | ||||
|             "{role} thinks {pronoun} little {affectionate_term} is getting close~", | ||||
|             "it's ok, {role}'s here for you~", | ||||
|             "oh, darling, you're almost there~", | ||||
|             "does {role}'s little {affectionate_term} need a bit of a break~?", | ||||
|             "oops~! {role} loves you anyways~", | ||||
|             "try again for {role}, {affectionate_term}~", | ||||
|             "don't worry, {role} knows you can do it~" | ||||
|         ], | ||||
|         Situation.OVERFLOW: [ | ||||
|             "{role} has executed too many times and needs to take a nap~" | ||||
|         ] | ||||
|     }, | ||||
|     "ominous": { | ||||
|         Situation.POSITIVE: [ | ||||
|             "What you have set in motion today will be remembered for aeons to come!", | ||||
|             "{role} will see to it that {pronoun} little {affectionate_term}'s name is feared~", | ||||
|             "{role} is proud of the evil seed {pronoun} {affectionate_term} has planted into this accursed world" | ||||
|         ], | ||||
|         Situation.NEGATIVE: [ | ||||
|             "Ah, failure? {role} will make sure the stars are right next time", | ||||
|             "Does {role}'s little {affectionate_term} need more time for worship~?", | ||||
|             "May the mark of the beast stain your flesh forever, {role} will haunt your soul forevermore" | ||||
|         ], | ||||
|         Situation.OVERFLOW: [ | ||||
|             "THOU HAST DRUNK TOO DEEPLY OF THE FONT" | ||||
|         ] | ||||
|     }, | ||||
|     "thirsty": { | ||||
|         "spiciness": "thirsty", | ||||
|         Situation.POSITIVE: [ | ||||
|             "*tugs your leash*\nthat's a VERY good {affectionate_term}~", | ||||
|             "*runs {pronoun} fingers through your hair* good {affectionate_term}~ keep going~", | ||||
|             "*smooches your forehead*\ngood job~", | ||||
|             "*nibbles on your ear*\nthat's right~\nkeep going~", | ||||
|             "*pats your butt*\nthat's a good {affectionate_term}~", | ||||
|             "*drags {pronoun} nail along your cheek*\nsuch a good {affectionate_term}~", | ||||
|             "*bites {pronoun} lip*\nmhmm~", | ||||
|             "give {role} a kiss~", | ||||
|             "*heavy breathing against your neck*" | ||||
|         ], | ||||
|         Situation.NEGATIVE: [ | ||||
|             "you're so cute when you're flustered~", | ||||
|             "do you think you're going to get a reward from {role} like that~?", | ||||
|             "*grabs your hair and pulls your head back*\nyou can do better than that for {role} can't you~?", | ||||
|             "if you don't learn how to code better, {role} is going to put you in time-out~", | ||||
|             "does {role} need to give {pronoun} little {affectionate_term} some special lessons~?", | ||||
|             "you need to work harder to please {role}~", | ||||
|             "gosh you must be flustered~", | ||||
|             "are you just keysmashing now~?\ncute~", | ||||
|             "is {role}'s little {affectionate_term} having trouble reaching the keyboard~?" | ||||
|         ], | ||||
|         Situation.OVERFLOW: [ | ||||
|             "you've been a bad little {affectionate_term} and worn out {role}~" | ||||
|         ] | ||||
|     }, | ||||
|     "yikes": { | ||||
|         "spiciness": "yikes", | ||||
|         Situation.POSITIVE: [ | ||||
|             "keep it up and {role} might let you cum you little {denigrating_term}~", | ||||
|             "good {denigrating_term}~\nyou've earned five minutes with the buzzy wand~", | ||||
|             "mmm~ come taste {role}'s {part}~", | ||||
|             "*slides {pronoun} finger in your mouth*\nthat's a good little {denigrating_term}~", | ||||
|             "you're so good with your fingers~\n{role} knows where {pronoun} {denigrating_term} should put them next~", | ||||
|             "{role} is getting hot~", | ||||
|             "that's a good {denigrating_term}~", | ||||
|             "yes~\nyes~~\nyes~~~", | ||||
|             "{role}'s going to keep {pronoun} good little {denigrating_term}~", | ||||
|             "open wide {denigrating_term}.\nyou've earned {role}'s {part}~", | ||||
|             "do you want {role}'s {part}?\nkeep this up and you'll earn it~", | ||||
|             "oooh~ what a good {denigrating_term} you are~" | ||||
|         ], | ||||
|         Situation.NEGATIVE: [ | ||||
|             "you filthy {denigrating_term}~\nyou made a mess, now clean it up~\nwith your tongue~", | ||||
|             "*picks you up by the throat*\npathetic~", | ||||
|             "*drags {pronoun} claws down your back*\ndo it again~", | ||||
|             "*brandishes {pronoun} paddle*\ndon't make me use this~", | ||||
|             "{denigrating_term}.\n{denigrating_term}~\n{denigrating_term}~~", | ||||
|             "get on your knees and beg {role} for forgiveness you {denigrating_term}~", | ||||
|             "{role} doesn't think {pronoun} little {denigrating_term} should have permission to wear clothes anymore~", | ||||
|             "never forget you belong to {role}~", | ||||
|             "does {role} need to put you in the {denigrating_term} wiggler~?", | ||||
|             "{role} is starting to wonder if you should just give up and become {pronoun} breeding stock~", | ||||
|             "on your knees {denigrating_term}~", | ||||
|             "oh dear. {role} is not pleased", | ||||
|             "one spank per error sounds appropriate, don't you think {denigrating_term}?", | ||||
|             "no more {part} for you {denigrating_term}" | ||||
|         ], | ||||
|         Situation.OVERFLOW: [ | ||||
|             "brats like you don't get to talk to {role}" | ||||
|         ] | ||||
|     } | ||||
| } | ||||
| logger = logging.Logger(__name__) | ||||
|  | ||||
|  | ||||
| class colors: | ||||
|     HEADER = '\033[95m' | ||||
| @@ -142,3 +20,89 @@ class colors: | ||||
|     BOLD = '\033[1m' | ||||
|     UNDERLINE = '\033[4m' | ||||
|     ENDC = '\033[0m' | ||||
|  | ||||
|  | ||||
| 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" | ||||
| COMPILED_CONFIG_FILE_NAME = "compiled-mommy.json" | ||||
|  | ||||
| IS_VENV = sys.prefix != sys.base_prefix | ||||
| VENV_DIRECTORY = Path(sys.prefix) | ||||
|  | ||||
| def get_config_file() -> Optional[Path]: | ||||
|     config_files = [] | ||||
|     if IS_VENV: | ||||
|         config_files.extend([ | ||||
|             VENV_DIRECTORY / "python-mommy.toml", | ||||
|             VENV_DIRECTORY / "mommy.toml", | ||||
|         ]) | ||||
|     config_files.extend([ | ||||
|         CONFIG_DIRECTORY / "python-mommy.toml", | ||||
|         CONFIG_DIRECTORY / "mommy.toml", | ||||
|     ]) | ||||
|  | ||||
|     for f in config_files: | ||||
|         if f.exists(): | ||||
|             return f | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| def get_compiled_config_file() -> Path: | ||||
|     compiled_config_files = [ | ||||
|         VENV_DIRECTORY / "compiled-mommy.json", | ||||
|         CONFIG_DIRECTORY / "compiled-mommy.json", | ||||
|     ] | ||||
|  | ||||
|     for f in compiled_config_files: | ||||
|         if f.exists(): | ||||
|             return f | ||||
|          | ||||
|     raise Exception("couldn't find compiled config file") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user