diff --git a/pyproject.toml b/pyproject.toml index dd01be8..110eb7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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] diff --git a/python_mommy_venv/__init__.py b/python_mommy_venv/__init__.py index 48a07b0..46f362a 100644 --- a/python_mommy_venv/__init__.py +++ b/python_mommy_venv/__init__.py @@ -2,24 +2,33 @@ import random import subprocess import sys from typing import Optional -import os -import re -import signal +import json -from .config import get_mood, get_template_values -from .static import RESPONSES, Situation, colors +from .responses import COMPILED_CONFIG_FILE +from .static import 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: 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(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") diff --git a/python_mommy_venv/__main__.py b/python_mommy_venv/__main__.py index e8bd205..e80226b 100644 --- a/python_mommy_venv/__main__.py +++ b/python_mommy_venv/__main__.py @@ -5,37 +5,21 @@ import stat 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 def development(): s = "positive" 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}" @@ -43,7 +27,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) """ @@ -97,7 +81,10 @@ def mommify_pip(path: Path): with path.open("w") as f: f.write(text) + def mommify_venv(): + compile_config() + v = ".venv" if len(sys.argv) > 1: v = sys.argv[1] diff --git a/python_mommy_venv/config.py b/python_mommy_venv/config.py deleted file mode 100644 index 3025005..0000000 --- a/python_mommy_venv/config.py +++ /dev/null @@ -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 diff --git a/python_mommy_venv/responses.py b/python_mommy_venv/responses.py new file mode 100644 index 0000000..8affb82 --- /dev/null +++ b/python_mommy_venv/responses.py @@ -0,0 +1,174 @@ +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) diff --git a/python_mommy_venv/static.py b/python_mommy_venv/static.py index 499bf0b..ba07794 100644 --- a/python_mommy_venv/static.py +++ b/python_mommy_venv/static.py @@ -1,136 +1,4 @@ from __future__ import annotations -from enum import Enum - -class Situation(Enum): - POSITIVE = "positive" - NEGATIVE = "negative" - OVERFLOW = "overflow " - - -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}" - ] - } -} class colors: HEADER = '\033[95m' diff --git a/test.py b/test.py new file mode 100644 index 0000000..f184c11 --- /dev/null +++ b/test.py @@ -0,0 +1 @@ +print("success") \ No newline at end of file