Compare commits
	
		
			10 Commits
		
	
	
		
			ac7360ffe8
			...
			3a7b46219e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3a7b46219e | |||
|  | ad1aff0438 | ||
|  | 3213e8a21f | ||
|  | 94c585fa7a | ||
|  | a8a1f425cc | ||
|  | be95c352d6 | ||
|  | d1fe9d55d5 | ||
|  | 350f583b89 | ||
|  | ad61ad4643 | ||
|  | cc5b7ae55e | 
| @@ -1,7 +1,7 @@ | |||||||
| [project] | [project] | ||||||
| description = "Mommy's here to support you when running python (in a virtual enviroment)~ ❤️" | description = "Mommy's here to support you when running python (in a virtual enviroment)~ ❤️" | ||||||
| name = "python_mommy_venv" | name = "python_mommy_venv" | ||||||
| dependencies = ["toml"] | dependencies = ["toml", "requests"] | ||||||
| authors = [] | authors = [] | ||||||
| readme = "README.md" | readme = "README.md" | ||||||
| requires-python = ">=3.9" | requires-python = ">=3.9" | ||||||
| @@ -15,7 +15,6 @@ license-files = ["LICENSE"] | |||||||
|  |  | ||||||
| [project.scripts] | [project.scripts] | ||||||
| python-mommy-dev = "python_mommy_venv.__main__:development" | 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" | mommify-venv = "python_mommy_venv.__main__:mommify_venv" | ||||||
|  |  | ||||||
| [build-system] | [build-system] | ||||||
|   | |||||||
| @@ -1,21 +1,32 @@ | |||||||
| import random | import random | ||||||
| import sys | import sys | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  | import json | ||||||
|  |  | ||||||
| from .config import get_mood, get_template_values | from .static import colors, get_compiled_config_file | ||||||
| from .static import RESPONSES, Situation, colors |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_response(situation: Situation, colorize: Optional[bool] = None): | def get_response_from_situation(situation: str, colorize: Optional[bool] = None): | ||||||
|     if colorize is None: |     if colorize is None: | ||||||
|         colorize = sys.stdout.isatty() |         colorize = sys.stdout.isatty() | ||||||
|  |  | ||||||
|     # get message |     # get message | ||||||
|     mood = get_mood() |     config = json.loads(get_compiled_config_file().read_text()) | ||||||
|     template = random.choice(RESPONSES[mood][situation]) |     existing_moods = list(config["moods"].keys()) | ||||||
|     message = template.format(**get_template_values(mood)) |     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 |     # return message | ||||||
|     if not colorize: |     if not colorize: | ||||||
|         return message |         return message | ||||||
|     return colors.BOLD + message + colors.ENDC |     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,13 +3,11 @@ from pathlib import Path | |||||||
| import stat | import stat | ||||||
| import subprocess | import subprocess | ||||||
| import logging | import logging | ||||||
|  | import json | ||||||
|  | import argparse | ||||||
|  |  | ||||||
| import toml | from .responses import compile_config | ||||||
|  | from .static import IS_VENV, VENV_DIRECTORY, CONFIG_DIRECTORY, COMPILED_CONFIG_FILE_NAME | ||||||
| from . import get_response |  | ||||||
| from .static import Situation |  | ||||||
| from .config import CONFIG_FILES, CONFIG_DIRECTORY, generate_current_configuration |  | ||||||
|  |  | ||||||
|  |  | ||||||
| logging.basicConfig( | logging.basicConfig( | ||||||
|     format='%(message)s', |     format='%(message)s', | ||||||
| @@ -21,7 +19,18 @@ log_level = logging.INFO | |||||||
| mommy_logger = logging.getLogger("mommy") | mommy_logger = logging.getLogger("mommy") | ||||||
| mommy_logger.setLevel(logging.INFO) | mommy_logger.setLevel(logging.INFO) | ||||||
| serious_logger = logging.getLogger("serious") | 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(): | def development(): | ||||||
| @@ -29,28 +38,14 @@ def development(): | |||||||
|     if len(sys.argv) > 1: |     if len(sys.argv) > 1: | ||||||
|         s = 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} | WRAPPER_TEMPLATE = """#!{inner_bin} | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| import sys, subprocess | import sys, subprocess | ||||||
| from python_mommy_venv import get_response, Situation | from python_mommy_venv import get_response | ||||||
|  |  | ||||||
|  |  | ||||||
| INTERPRETER = "{inner_bin}" | INTERPRETER = "{inner_bin}" | ||||||
| @@ -58,7 +53,7 @@ result = subprocess.run([INTERPRETER] + sys.argv[1:]) | |||||||
| code = result.returncode | code = result.returncode | ||||||
|  |  | ||||||
| print() | print() | ||||||
| print(get_response(Situation.POSITIVE if code == 0 else Situation.NEGATIVE)) | print(get_response(code)) | ||||||
| exit(code=code) | exit(code=code) | ||||||
| """ | """ | ||||||
|  |  | ||||||
| @@ -98,6 +93,13 @@ PIP_HOOK = """# GENERATED BY MOMMY | |||||||
|     sys.exit(code)""" |     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): | def wrap_interpreter(path: Path): | ||||||
|     mommy_logger.info("mommy found a symlink to an interpreter~ %s", str(path)) |     mommy_logger.info("mommy found a symlink to an interpreter~ %s", str(path)) | ||||||
|     serious_logger.info("interpreter symlink found at %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(): | def mommify_venv(): | ||||||
|  |     parser = argparse.ArgumentParser(description="patch the virtual environment to use mommy") | ||||||
|      |      | ||||||
|     v = ".venv" |     parser.add_argument( | ||||||
|     if len(sys.argv) > 1: |         "-v", "--verbose", | ||||||
|         v = sys.argv[1] |         action="store_true", | ||||||
|  |         help="enable verbose and serious output" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     bin_path = Path(v, "bin") |     parser.add_argument( | ||||||
|  |         "-l", "--local", | ||||||
|  |         action="store_true", | ||||||
|  |         help="compile the config only for the current virtual environment" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     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() |     bin_path = bin_path.resolve() | ||||||
|  |  | ||||||
|     mommy_logger.info("mommy looks in %s to mess your system up~ <33", str(bin_path)) |     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(): |         if path.is_symlink(): | ||||||
|             # could be python interpreter |             # could be python interpreter | ||||||
|             # check for both just to be more expressive |             # check for both just to be more expressive | ||||||
|             if name.startswith("inner_") or not name.startswith("python"): |             if name.startswith("inner_"): | ||||||
|                 continue |                 continue | ||||||
|              |              | ||||||
|             if subprocess.run([str(path), '-c', '"exit()"']) != 0: |             if subprocess.run([str(path), '-c', '"exit(0)"']).returncode != 0: | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             wrap_interpreter(path) |             wrap_interpreter(path) | ||||||
| @@ -176,6 +205,3 @@ def mommify_venv(): | |||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             install_pip_hook(path) |             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 __future__ import annotations | ||||||
| from enum import Enum |  | ||||||
|  |  | ||||||
| class Situation(Enum): | from pathlib import Path | ||||||
|     POSITIVE = "positive" | import os | ||||||
|     NEGATIVE = "negative" | import logging | ||||||
|     OVERFLOW = "overflow " | from typing import Optional | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  |  | ||||||
| RESPONSES = { | logger = logging.Logger(__name__) | ||||||
|     "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}" |  | ||||||
|         ] |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class colors: | class colors: | ||||||
|     HEADER = '\033[95m' |     HEADER = '\033[95m' | ||||||
| @@ -142,3 +20,89 @@ class colors: | |||||||
|     BOLD = '\033[1m' |     BOLD = '\033[1m' | ||||||
|     UNDERLINE = '\033[4m' |     UNDERLINE = '\033[4m' | ||||||
|     ENDC = '\033[0m' |     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