Compare commits

...

28 Commits

Author SHA1 Message Date
Hazel Noack
9040b26279 implemented option to disable requests 2025-07-30 12:16:34 +02:00
Hazel Noack
98d656b2fe validate type of vars from config file 2025-07-30 12:00:32 +02:00
Hazel Noack
abadff31d8 imporved logging 2025-07-30 11:55:33 +02:00
Hazel Noack
ba36851336 changed the returncode to make sure it detects python 2025-07-30 11:16:03 +02:00
Hazel Noack
d9e6bac410 rewrote resolving the symlinks in comprehension 2025-07-30 11:08:27 +02:00
Hazel Noack
69f6a11874 feat: implemented error handling for empty lists 2025-07-30 11:00:18 +02:00
amnesia
8e8409afc4 typo 2025-07-29 21:13:44 +02:00
fd51a0625f updated responses json 2025-07-29 20:38:12 +02:00
2162cf78cd replaced response json 2025-07-29 20:35:22 +02:00
8036dc33b3 fixed symlinc resolving 2025-07-29 20:32:16 +02:00
Hazel Noack
d968795628 cli script to recompile config 2025-07-29 12:46:56 +02:00
Hazel Noack
05f7003ea8 removed development cli script 2025-07-29 12:38:08 +02:00
3a7b46219e Merge pull request 'building_config' (#1) from building_config into main
Reviewed-on: #1
2025-07-29 10:28:01 +00:00
Hazel Noack
ad1aff0438 completely disable bad logger 2025-07-29 12:24:47 +02:00
Hazel Noack
3213e8a21f added option for local compile 2025-07-29 12:22:41 +02:00
Hazel Noack
94c585fa7a added verbose option 2025-07-29 12:19:06 +02:00
Hazel Noack
a8a1f425cc ensure running in venv 2025-07-29 12:08:27 +02:00
Hazel Noack
be95c352d6 cleaned up compiled config file 2025-07-29 11:56:39 +02:00
Hazel Noack
d1fe9d55d5 Merge branch 'main' into building_config 2025-07-29 11:33:21 +02:00
Hazel Noack
ac7360ffe8 added logging 2025-07-29 11:31:49 +02:00
Hazel Noack
350f583b89 draft 2025-07-28 17:39:44 +02:00
Hazel Noack
ad61ad4643 fetching cargo mommy prompts on mommify 2025-07-28 17:27:41 +02:00
Hazel Noack
cc5b7ae55e implemented compilation of config file to remove complexity from actual library 2025-07-28 17:16:45 +02:00
Hazel Noack
be024389ba fetching responses json 2025-07-28 15:29:17 +02:00
Hazel Noack
29098500e2 added pip hook to mommify newly installed packages 2025-07-28 14:04:07 +02:00
Hazel Noack
aff54b38ed added generation of config file 2025-07-28 12:40:27 +02:00
Hazel Noack
6f589b86e2 fix loading of config file 2025-07-28 12:26:08 +02:00
Hazel Noack
bd9fcd1edc rewrote config to be compliant with cargo mommy 2025-07-28 12:20:05 +02:00
10 changed files with 696 additions and 286 deletions

View File

@@ -9,8 +9,7 @@ THIS IS A PORT OF [Gankra/cargo-mommy](https://github.com/Gankra/cargo-mommy)
# TODO
- edit executable in .venv/pyenv.cfg
- link it to a mommy wrapper
- make compatibility with https://github.com/Gankra/cargo-mommy/blob/main/responses.json
# Installation

20
generate Executable file
View File

@@ -0,0 +1,20 @@
#!.venv/bin/python3
import requests
from pathlib import Path
import json
CARGO_MOMMY_DATA = "https://raw.githubusercontent.com/Gankra/cargo-mommy/refs/heads/main/responses.json"
MODULE_PATH = Path("python_mommy_venv")
if __name__ == "__main__":
print("generating stuff")
res = requests.get(CARGO_MOMMY_DATA)
if not res.ok:
raise Exception(f"couldn't fetch {CARGO_MOMMY_DATA} ({res.status_code})")
print(f"writing {Path(MODULE_PATH, 'responses.json')}")
with Path(MODULE_PATH, "responses.json").open("w") as f:
json.dump(res.json(), f, indent=4)

View File

@@ -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"
@@ -14,7 +14,7 @@ version = "0.0.0"
license-files = ["LICENSE"]
[project.scripts]
python-mommy-dev = "python_mommy_venv.__main__:development"
mommify-venv-compile = "python_mommy_venv.__main__:cli_compile_config"
mommify-venv = "python_mommy_venv.__main__:mommify_venv"
[build-system]

View File

@@ -1,29 +1,32 @@
import random
import subprocess
import sys
from typing import Optional
import os
import re
import signal
import json
from .config import CONFIG
from .static import RESPONSES, Situation, colors
from .static import colors, get_compiled_config_file
def _expand_template(template: str) -> str:
for key, value in CONFIG.items():
template = template.replace(key, random.choice(value))
return template + " " + random.choice(CONFIG["MOMMYS_EMOTES"])
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
possible_templates = RESPONSES[random.choice(CONFIG["MOMMYS_MOODS"])][situation]
message = _expand_template(random.choice(possible_templates))
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")

View File

@@ -1,25 +1,44 @@
import sys
from pathlib import Path
import stat
import subprocess
import logging
import json
import argparse
from . import get_response
from .static import Situation
from .responses import compile_config
from .static import IS_VENV, VENV_DIRECTORY, CONFIG_DIRECTORY, COMPILED_CONFIG_FILE_NAME
from ntpath import devnull
logging.basicConfig(
format='%(message)s',
force=True,
)
log_level = logging.INFO
mommy_logger = logging.getLogger("mommy")
mommy_logger.setLevel(logging.INFO)
serious_logger = logging.getLogger("serious")
serious_logger.setLevel(50)
def development():
s = "positive"
if len(sys.argv) > 1:
s = sys.argv[1]
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)
print(get_response(Situation(s)))
TEMPLATE = """#!{inner_bin}
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}"
@@ -27,49 +46,207 @@ 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)
"""
def mommify_venv():
v = ".venv"
if len(sys.argv) > 1:
v = sys.argv[1]
PIP_HOOK = """# GENERATED BY MOMMY
code = main()
bin_path = Path(v, "bin")
bin_path = bin_path.resolve()
print(bin_path)
from pathlib import Path
bin_path = Path(".venv", "bin")
python_interpreter_wrappers = []
for path in bin_path.iterdir():
if path.is_symlink() and path.name.startswith("inner_"):
python_interpreter_wrappers.append(path.name.replace("inner_", "", 1))
for path in bin_path.iterdir():
if not path.is_symlink():
if path.is_symlink():
continue
name = path.name
if name.startswith("inner_"):
continue
target = path.resolve()
print("")
print(f"modifying {name} ({target})")
# creating inner symlink
inner_bin = Path(bin_path, "inner_" + name)
if inner_bin.exists():
print(f"inner symlink does already exist {inner_bin}")
print("skipping")
if path.name in python_interpreter_wrappers:
continue
print(f"creating symlink: {inner_bin} -> {target}")
Path(bin_path, "inner_" + name).symlink_to(target)
text: str
with path.open("r") as f:
text = f.read()
# remove original symlink
print(f"removing original symlink: {path}")
path.unlink()
first_line = text.split("\\n")[0]
if not ("inner_" in first_line and first_line.startswith("#!")):
continue
# creating the wrapper string
print("writing wrapper script")
print(f"mommifying " + str(path))
text = text.replace("inner_", "", 1)
with path.open("w") as f:
f.write(TEMPLATE.format(inner_bin=str(inner_bin)))
print("making wrapper script executable")
path.chmod(path.stat().st_mode | stat.S_IEXEC)
f.write(text)
sys.exit(code)"""
def assert_venv(only_warn: bool = False):
if not IS_VENV:
mommy_logger.error("mommy doesn't run in a virtual environment~")
serious_logger.error("this should run in a virtual environment")
if not only_warn:
exit(1)
def write_compile_config(local: bool, disable_requests: bool = False):
assert_venv(only_warn=not local)
compiled_base_dir = VENV_DIRECTORY if local else CONFIG_DIRECTORY
compiled_config_file = compiled_base_dir / COMPILED_CONFIG_FILE_NAME
compiled = compile_config(disable_requests=disable_requests)
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 local:
(VENV_DIRECTORY / COMPILED_CONFIG_FILE_NAME).unlink(missing_ok=True)
def wrap_interpreter(path: Path, symlink_target: Path):
mommy_logger.info("mommy found a symlink to an interpreter~ %s", str(path))
serious_logger.info("interpreter symlink found at %s", str(path))
inner_symlink = path.parent / ("inner_" + path.name)
if inner_symlink.exists():
raise Exception("inner symlink somehow already exists. This shouldn't happen because of prior checks")
mommy_logger.info("mommy shows her girl where the interpreter is: %s -> %s", inner_symlink, symlink_target)
serious_logger.info("creating symlink: %s -> %s", inner_symlink, symlink_target)
inner_symlink.symlink_to(symlink_target)
# remove original symlink
mommy_logger.info("mommy deletes the original interpreter~ %s", path)
serious_logger.info("deleting original symlink %s", path)
path.unlink()
# creating the wrapper string
mommy_logger.info("mommy writes wrapper script as %s", Path)
serious_logger.info("writing wrapper script at %s", path)
with path.open("w") as f:
f.write(WRAPPER_TEMPLATE.format(inner_bin=str(inner_symlink)))
serious_logger.info("making wrapper script executable")
path.chmod(path.stat().st_mode | stat.S_IEXEC)
def install_pip_hook(path: Path):
text: str
with path.open("r") as f:
text = f.read()
if "# GENERATED BY MOMMY" in text:
mommy_logger.info("ahhhhh mommy already watches %s", str(path))
serious_logger.info("pip hook already installed at %s", str(path))
return
mommy_logger.info("mommy needs to keep an eye on this little pip~ %s", str(path))
serious_logger.info("installing pip hook at %s", str(path))
text = text.replace("sys.exit(main())", PIP_HOOK, 1)
with path.open("w") as f:
f.write(text)
def cli_compile_config():
parser = argparse.ArgumentParser(description="only recompile the config")
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="enable verbose and serious output"
)
parser.add_argument(
"-l", "--local",
action="store_true",
help="compile the config only for the current virtual environment"
)
parser.add_argument(
"-r", "--no-requests",
action="store_true",
help="by default if makes one request to GitHub to fetch the newest responses, this disables that"
)
args = parser.parse_args()
config_logging(args.verbose)
write_compile_config(args.local, disable_requests=args.no_requests)
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"
)
parser.add_argument(
"-l", "--local",
action="store_true",
help="compile the config only for the current virtual environment"
)
parser.add_argument(
"-r", "--no-requests",
action="store_true",
help="by default if makes one request to GitHub to fetch the newest responses, this disables that"
)
args = parser.parse_args()
config_logging(args.verbose)
assert_venv()
write_compile_config(args.local)
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))
serious_logger.info("scanning binary directory of venv at %s", str(bin_path))
# resolving the symlinks before making edits to anything because else it will mess up the resolving
# and link to the wrapper instead of the original script
resolved_symlinks = {
path.name: path.resolve()
for path in bin_path.iterdir()
if path.is_symlink()
}
serious_logger.debug("resolved symlinks:\n%s", "\n".join(
f"\t{name} => {str(target)}"
for name, target in resolved_symlinks.items()
))
for path in list(bin_path.iterdir()):
name = path.name
if path.is_symlink():
# could be python interpreter
# check for both just to be more expressive
if name.startswith("inner_"):
continue
RANDOM_RETURNCODE = 161
if subprocess.run([str(path), '-c', f'exit({RANDOM_RETURNCODE})']).returncode != RANDOM_RETURNCODE:
continue
wrap_interpreter(path, resolved_symlinks[path.name])
else:
# could be pip
if not name.startswith("pip"):
continue
install_pip_hook(path)

View File

@@ -1,145 +0,0 @@
from typing import Optional, List
import os
from os.path import expandvars
from sys import platform
import logging
from pathlib import Path
import configparser
import toml
from .static import RESPONSES
logger = logging.Logger("mommy_config")
PREFIXES = [
"PYTHON", # first one is always the prefix of the current program
"CARGO",
]
def _get_var(key: str, fallback: str) -> List[str]:
value = os.environ.get(
PREFIXES[0] + "_" + key,
os.environ.get(
key,
None
)
)
if value is None:
for prefix in PREFIXES[1:]:
value = os.environ.get(prefix + "_" + key, None)
if value != None:
break
return (value or fallback).split("/")
_DEFAULT_CONFIG = {key: _get_var(key, value) for key, value in {
"MOMMYS_ROLE": "mommy",
"MOMMYS_PRONOUNS": "her",
"MOMMYS_LITTLE": "girl",
"MOMMYS_EMOTES": "❤️/💖/💗/💓/💞",
"MOMMYS_PARTS": "milk",
"MOMMYS_FUCKING": "slut/toy/pet/pervert/whore",
# needs validation
"MOMMYS_MOODS": "chill",
}.items()}
CONFIG = {}
def load_config(data: Optional[dict] = None):
global CONFIG
data = data if data is not None else {}
data = {
**_DEFAULT_CONFIG,
**data,
}
# convert toml keys from snake_case to UPPER_CASE
data = {
key.upper(): value
for key, value in data.items()
}
# validate needed values
unfiltered_moods = data["MOMMYS_MOODS"]
data["MOMMYS_MOODS"] = filtered_moods = []
for mood in unfiltered_moods:
if mood in RESPONSES:
filtered_moods.append(mood)
else:
logger.warning("mood %s isn't supported", mood)
CONFIG = data
load_config()
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:
if not config_file.exists():
return False
with config_file.open("r") as f:
data = toml.load(f)
load_config(data=data)
return True
for c in CONFIG_FILES:
if load_config_file(c):
break

View File

@@ -0,0 +1,173 @@
{
"moods": {
"chill": {
"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}~"
],
"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~"
],
"overflow": [
"{role} has executed too many times and needs to take a nap~"
]
},
"ominous": {
"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"
],
"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"
],
"overflow": [
"THOU HAST DRUNK TOO DEEPLY OF THE FONT"
]
},
"thirsty": {
"spiciness": "thirsty",
"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*"
],
"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~?"
],
"overflow": [
"you've been a bad little {affectionate_term} and worn out {role}~"
]
},
"yikes": {
"spiciness": "yikes",
"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~"
],
"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}"
],
"overflow": [
"brats like you don't get to talk to {role}"
]
}
},
"vars": {
"mood": {
"defaults": [
"chill"
]
},
"emote": {
"defaults": [
"\u2764\ufe0f",
"\ud83d\udc96",
"\ud83d\udc97",
"\ud83d\udc93",
"\ud83d\udc9e"
]
},
"pronoun": {
"defaults": [
"her"
]
},
"role": {
"defaults": []
},
"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"
]
}
}
}

View File

@@ -0,0 +1,166 @@
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/Gankra/cargo-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:
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)
try:
r = requests.get(RESPONSES_URL)
data = r.json()
except requests.exceptions.ConnectionError:
mommy_logger.info("mommy couldn't fetch the url~")
serious_logger.info("couldnt fetch the url")
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:
c = _load_config_file(config_file)
serious_logger.debug(
"config at %s:\n%s\n",
config_file,
json.dumps(c, indent=4)
)
config["mood"] = c.get("moods", config["mood"])
c_vars: dict = c.get("vars", {})
# validate the config var values
for key, val in c_vars.items():
if not isinstance(val, list):
mommy_logger.error("mommy needs the value of %s to be a list~", key)
serious_logger.error("the value of %s is not a list", key)
exit(1)
config.update(c_vars)
# 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 empty variables
empty_values = []
for key, value in config.items():
if len(value) == 0:
empty_values.append(key)
if len(empty_values) > 0:
empty_values_sting = ", ".join(empty_values)
mommy_logger.error(
"mommy is very displeased that you didn't config the key(s) %s",
empty_values_sting,
)
serious_logger.error(
"the following keys have empty values and need to be configured: %s",
empty_values_sting
)
exit(1)
# 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

View File

@@ -1,84 +1,14 @@
from enum import Enum
from __future__ import annotations
class Situation(Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
from pathlib import Path
import os
import logging
from typing import Optional
import sys
logger = logging.Logger(__name__)
RESPONSES = {
"chill": {
Situation.POSITIVE: [
"*pets your head*",
"*gives you scritches*",
"you're such a smart cookie~",
"that's a good MOMMYS_LITTLE~",
"MOMMYS_ROLE thinks MOMMYS_PRONOUNS little MOMMYS_LITTLE earned a big hug~",
"good MOMMYS_LITTLE~\nMOMMYS_ROLE's so proud of you~",
"aww, what a good MOMMYS_LITTLE~\nMOMMYS_ROLE knew you could do it~",
"you did it~!",
"MOMMYS_ROLE loves you~",
"*gives you a sticker*"
],
Situation.NEGATIVE: [
"MOMMYS_ROLE believes in you~",
"don't forget to hydrate~",
"aww, you'll get it next time~",
"do you need MOMMYS_ROLE's help~?",
"MOMMYS_ROLE still loves you no matter what~",
"oh no did MOMMYS_ROLE's little MOMMYS_LITTLE make a big mess~?",
"MOMMYS_ROLE knows MOMMYS_PRONOUNS little MOMMYS_LITTLE can do better~",
"MOMMYS_ROLE still loves you~",
"just a little further, sweetie~"
]
},
"thirsty": {
Situation.POSITIVE: [
"*tugs your leash*\nthat's a VERY good MOMMYS_LITTLE~",
"*runs MOMMYS_PRONOUNS fingers through your hair* good MOMMYS_LITTLE~ keep going~",
"*smooches your forehead*\ngood job~",
"*nibbles on your ear*\nthat's right~\nkeep going~",
"*pats your butt*\nthat's a good MOMMYS_LITTLE~",
"*drags MOMMYS_PRONOUNS nail along your cheek*\nsuch a good MOMMYS_LITTLE~",
"*bites MOMMYS_PRONOUNS lip*\nmhmm~",
"give MOMMYS_PRONOUNS a kiss~",
"*heavy breathing against your neck*"
],
Situation.NEGATIVE: [
"do you think you're going to get a reward from MOMMYS_ROLE like that~?",
"*grabs your hair and pulls your head back*\nyou can do better than that for MOMMYS_ROLE can't you~?",
"if you don't learn how to code better, MOMMYS_ROLE is going to put you in time-out~",
"does MOMMYS_ROLE need to give MOMMYS_PRONOUNS little MOMMYS_LITTLE some special lessons~?",
"you need to work harder to please MOMMYS_ROLE~",
"gosh you must be flustered~",
"are you just keysmashing now~?\ncute~",
"is MOMMYS_ROLE's little MOMMYS_LITTLE having trouble reaching the keyboard~?"
]
},
"yikes": {
Situation.POSITIVE: [
"keep it up and MOMMYS_ROLE might let you cum you little MOMMYS_FUCKING~",
"good MOMMYS_FUCKING~\nyou've earned five minutes with the buzzy wand~",
"mmm~ come taste MOMMYS_ROLE's MOMMYS_PARTS~",
"*slides MOMMYS_PRONOUNS finger in your mouth*\nthat's a good little MOMMYS_FUCKING~",
"you're so good with your fingers~\nMOMMYS_ROLE knows where MOMMYS_PRONOUNS MOMMYS_FUCKING should put them next~",
"MOMMYS_ROLE is getting hot~",
"that's a good MOMMYS_FUCKING~",
"yes~\nyes~~\nyes~~~",
"MOMMYS_ROLE's going to keep MOMMYS_PRONOUNS good little MOMMYS_FUCKING~"
],
Situation.NEGATIVE: [
"you filthy MOMMYS_FUCKING~\nyou made a mess, now clean it up~\nwith your tongue~",
"*picks you up by the throat*\npathetic~",
"*drags MOMMYS_PRONOUNS claws down your back*\ndo it again~",
"*brandishes MOMMYS_PRONOUNS paddle*\ndon't make me use this~",
"MOMMYS_FUCKING.\nMOMMYS_FUCKING~\nMOMMYS_FUCKING~~",
"get on your knees and beg MOMMYS_ROLE for forgiveness you MOMMYS_FUCKING~",
"MOMMYS_ROLE doesn't think MOMMYS_PRONOUNS little MOMMYS_FUCKING should have permission to wear clothes anymore~",
"never forget you belong to MOMMYS_ROLE~",
"does MOMMYS_ROLE need to put you in the MOMMYS_FUCKING wiggler~?",
"MOMMYS_ROLE is starting to wonder if you should just give up and become MOMMYS_PRONOUNS breeding stock~"
]
}
}
class colors:
HEADER = '\033[95m'
@@ -90,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")

1
test.py Normal file
View File

@@ -0,0 +1 @@
print("success")