Last active
April 16, 2022 16:07
-
-
Save perryBunn/489a2edc595305e0312960af8fdcb7bc to your computer and use it in GitHub Desktop.
Eu4_cheeser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
""" | |
Description: This will run at a regular interval to make copies of the EU4 save | |
specified. Creating backups of Ironman games to fall back to. | |
""" | |
from datetime import datetime | |
from logging import Logger | |
from pathlib import Path | |
from shutil import copyfile | |
from time import sleep | |
from typing import Union | |
import logging | |
import platform | |
import re | |
class ColoredFormatter(logging.Formatter): | |
""" Logging colored formatter | |
Adapted from Alexandra Zaharia, who adapted | |
this post https://stackoverflow.com/a/56944256/3638629 | |
""" | |
grey = '\x1b[38;21m' | |
blue = '\x1b[38;5;39m' | |
yellow = '\x1b[38;5;226m' | |
red = '\x1b[38;5;196m' | |
bold_red = '\x1b[31;1m' | |
reset = '\x1b[0m' | |
def __init__(self, fmt): | |
super().__init__() | |
self.fmt = fmt | |
self.formats = { | |
logging.DEBUG: self.grey + self.fmt + self.reset, | |
logging.INFO: self.blue + self.fmt + self.reset, | |
logging.WARNING: self.yellow + self.fmt + self.reset, | |
logging.ERROR: self.red + self.fmt + self.reset, | |
logging.CRITICAL: self.bold_red + self.fmt + self.reset | |
} | |
def format(self, record): | |
log_fmt = self.formats.get(record.levelno) | |
formatter = logging.Formatter(log_fmt) | |
return formatter.format(record) | |
def get_logger() -> Logger: | |
""" | |
Method will return a logging object with stdout and file handlers | |
Parameters: | |
None | |
Returns: | |
Logger: logging.Logger object | |
""" | |
log = logging.getLogger("eu4cheeser") | |
log.setLevel("DEBUG") | |
fmt = "%(asctime)s: %(name)s: %(levelname)s: %(message)s" | |
# Time formatted as YYYYmmddHHMM | |
date = datetime.now() | |
time = date.strftime("%Y%m%d%H%M") | |
log_dir = Path(f"LOGS/{time[:8]}") | |
log_dir.mkdir(exist_ok=True, parents=True) | |
file_name = (log_dir / f"{time}_cheeser.log").as_posix() | |
file_handle = logging.FileHandler(file_name, encoding="utf-8") | |
file_handle.setLevel("DEBUG") | |
stream_handle = logging.StreamHandler() | |
stream_handle.setLevel("INFO") | |
file_fmt = logging.Formatter(fmt) | |
stream_fmt = ColoredFormatter(fmt) | |
file_handle.setFormatter(file_fmt) | |
stream_handle.setFormatter(stream_fmt) | |
log.addHandler(stream_handle) | |
log.addHandler(file_handle) | |
return log | |
def get_response(question: str, valid_res: list=None, default=None, tries: int=1, | |
not_valid: list=None, permutations: bool=False) -> Union[None, str]: | |
""" Gets response to a question asked. | |
Parameters | |
---------- | |
question: str | |
Question to be asked to the user. | |
valid_res: list | |
List of valid responses. | |
tries: int | |
Number of tries that the user has to answer the question. | |
not_valid: list | |
List of not valid responses. | |
permutations: bool | |
Creates permutations of valid responses. Useful for questions where | |
multiple responses could be possible/ | |
Returns | |
------- | |
None | str | |
if an int is returned then the response from the user is not valid. | |
""" | |
if permutations and 2 <= len(valid_res): | |
upb = len(valid_res) | |
for i in range(0, upb - 1): | |
temp = valid_res[i] | |
for j in range(1, upb): | |
if i == j: | |
continue | |
temp+=valid_res[j] | |
valid_res.append(temp) | |
# Strips chars ' ' and ':' from the question | |
_question = question.strip() | |
_question = _question.strip(":") | |
quest = f"{_question}" | |
if valid_res: | |
quest += " (" | |
for res in valid_res: | |
quest += f"{res} " | |
quest = quest[:-1] # Remove trailing ' ' | |
quest += ")" | |
if default: | |
quest += f" [{default}]" | |
for i in range(tries): | |
res = input(f"{quest}: ") | |
if not res.strip() and default: | |
res = default | |
if not_valid: | |
for npat in not_valid: | |
nmatch = re.search(npat, res) | |
if nmatch: | |
print(f"{res} is an invalid response.") | |
continue | |
if valid_res is None or res in valid_res: | |
return res | |
print(f"{res} is an invalid response.") | |
return None | |
def list_saves(path: Path, logger) -> (list, dict): | |
names = [] | |
res = {} | |
game_paths = list(path.glob("*")) | |
if not len(game_paths) > 0: | |
logger.info("No save games found") | |
return None, None | |
print("EU4 Save Games:") | |
print("="*79) | |
for game in game_paths: | |
print(game.name) | |
res[game.name] = game | |
names.append(game.name) | |
return names, res | |
def which_os() -> Path: | |
system = platform.system() | |
if system == "Windows": | |
return Path("C:\\Users\\USER\\Documents\\Paradox Interactive\\Europa Universalis IV\\").resolve() | |
elif system == "Linux": | |
return Path("~/.local/share/Paradox Interactive/Europa Universalis IV/").expanduser() | |
elif system == "Darwin": | |
return Path("/Users/USER/Documents/Paradox Interactive/Europa Universalis IV/").resolve() | |
else: | |
raise RuntimeError("Unrecognized OS") | |
def make_backup(save: Path, date: str, logger: Logger): | |
logger.debug(f"Making backup of {save.name} at {date}") | |
backup_path = save.parent / f"{save.name[:-len(save.suffix)]}_cheeser" | |
copyfile(save, backup_path / f"{date}_{save.name}") | |
def main(): | |
logger = get_logger() | |
save_path = which_os() | |
interval = int(get_response("Interval in minutes:"))*60 | |
games, save_paths = list_saves(save_path / "save games", logger) | |
if games is None: | |
print("The program cannot continue without save games present. " | |
"Rerun the script once you have started a game.") | |
return | |
save_game = get_response("What save game do you want to watch?", | |
valid_res=games, tries=2) | |
save_game_path = save_paths[save_game] | |
try: | |
while True: | |
date = datetime.now().strftime("%Y%m%d%H%M") | |
try: | |
make_backup(save_game_path, date, logger) | |
except Exception as err: | |
logger.error(err) | |
logger.warning("Error encountered while trying to make backup. " | |
"Check logs for details.") | |
sleep(interval) | |
except KeyboardInterrupt: | |
print('interrupted!') | |
print("Stopping save interval. Bye!") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment