Merge pull request 'building_config' (#1) from building_config into main
Reviewed-on: #1
This commit is contained in:
commit
3a7b46219e
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user