refactor in setting.py, console.py and config tempalte

pull/1070/head
Drugsosos 3 years ago
parent 12f99f4e9d
commit 0fa2106af4
No known key found for this signature in database
GPG Key ID: 8E35176FE617E28D

@ -68,7 +68,7 @@ def shutdown():
if __name__ == "__main__":
config = settings.check_toml("utils/.config.template.toml", "config.toml")
config = settings.check_config(template_name="utils/.config.template.toml", name="config.toml")
config is False and exit()
try:
if config["settings"]["times_to_run"]:

@ -1,46 +1,203 @@
[reddit]
[reddit.creds]
client_id = { optional = false, nmin = 12, nmax = 30, explanation = "the ID of your Reddit app of SCRIPT type", example = "fFAGRNJru1FTz70BzhT3Zg", regex = "^[-a-zA-Z0-9._~+/]+=*$", input_error = "The client ID can only contain printable characters.", oob_error = "The ID should be over 12 and under 30 characters, double check your input." }
client_secret = { optional = false, nmin = 20, nmax = 40, explanation = "the SECRET of your Reddit app of SCRIPT type", example = "fFAGRNJru1FTz70BzhT3Zg", regex = "^[-a-zA-Z0-9._~+/]+=*$", input_error = "The client ID can only contain printable characters.", oob_error = "The secret should be over 20 and under 40 characters, double check your input." }
username = { optional = false, nmin = 3, nmax = 20, explanation = "the username of your reddit account", example = "JasonLovesDoggo", regex = "^[-_0-9a-zA-Z]+$", oob_error = "A username HAS to be between 3 and 20 characters" }
password = { optional = false, nmin = 8, explanation = "the password of your reddit account", example = "fFAGRNJru1FTz70BzhT3Zg", oob_error = "Password too short" }
2fa = { optional = true, type = "bool", options = [true,
false,
], default = false, explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False", example = true }
[reddit.creds.client_id]
optional = false
nmin = 12
nmax = 30
explanation = "the ID of your Reddit app of SCRIPT type"
example = "fFAGRNJru1FTz70BzhT3Zg"
regex = "^[-a-zA-Z0-9._~+/]+=*$"
input_error = "The client ID can only contain printable characters."
oob_error = "The ID should be over 12 and under 30 characters, double check your input."
[reddit.creds.client_secret]
optional = false
nmin = 20
nmax = 40
explanation = "the SECRET of your Reddit app of SCRIPT var_type"
example = "fFAGRNJru1FTz70BzhT3Zg"
regex = "^[-a-zA-Z0-9._~+/]+=*$"
input_error = "The client ID can only contain printable characters."
oob_error = "The secret should be over 20 and under 40 characters, double check your input."
[reddit.creds.username]
optional = false
nmin = 3
nmax = 20
explanation = "the username of your reddit account"
example = "JasonLovesDoggo"
regex = "^[-_0-9a-zA-Z]+$"
oob_error = "A username HAS to be between 3 and 20 characters"
[reddit.creds.password]
optional = false
nmin = 8
explanation = "the password of your reddit account"
example = "fFAGRNJru1FTz70BzhT3Zg"
oob_error = "Password too short"
[reddit.creds.2fa]
optional = true
var_type = "bool"
options = [true, false]
default = false
explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False"
example = true
[reddit.thread]
random = { optional = true, options = [true,
false,
], default = false, type = "bool", explanation = "If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: 'False'", example = "True" }
subreddit = { optional = false, regex = "[_0-9a-zA-Z]+$", nmin = 3, explanation = "what subreddit to pull posts from, the name of the sub, not the URL", example = "AskReddit", oob_error = "A subreddit name HAS to be between 3 and 20 characters" }
post_id = { optional = true, default = "", regex = "^((?!://|://)[+a-zA-Z0-9])*$", explanation = "Used if you want to use a specific post.", example = "urdtfx" }
max_comment_length = { default = 500, optional = false, nmin = 10, nmax = 10000, type = "int", explanation = "max number of characters a comment can have. default is 500", example = 500, oob_error = "the max comment length should be between 10 and 10000" }
post_lang = { default = "", optional = true, explanation = "The language you would like to translate to.", example = "es-cr" }
min_comments = { default = 20, optional = false, nmin = 15, type = "int", explanation = "The minimum number of comments a post should have to be included. default is 20", example = 29, oob_error = "the minimum number of comments should be between 15 and 999999" }
[reddit.thread.random]
optional = true
options = [true, false]
default = false
var_type = "bool"
explanation = "If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: 'False'"
example = "True"
[reddit.thread.subreddit]
optional = false
regex = "[_0-9a-zA-Z]+$"
nmin = 3
explanation = "what subreddit to pull posts from, the name of the sub, not the URL"
example = "AskReddit"
oob_error = "A subreddit name HAS to be between 3 and 20 characters"
[reddit.thread.post_id]
optional = true
default = ""
regex = "^((?!://|://)[+a-zA-Z0-9])*$"
explanation = "Used if you want to use a specific post."
example = "urdtfx"
[reddit.thread.max_comment_length]
default = 500
optional = false
nmin = 10
nmax = 10000
var_type = "int"
explanation = "max number of characters a comment can have. default is 500"
example = 500
oob_error = "the max comment length should be between 10 and 10000"
[reddit.thread.post_lang]
default = ""
optional = true
explanation = "The language you would like to translate to."
example = "es-cr"
[reddit.thread.min_comments]
default = 20
optional = false
nmin = 15
var_type = "int"
explanation = "The minimum number of comments a post should have to be included. default is 20"
example = 29
oob_error = "the minimum number of comments should be between 15 and 999999"
[settings]
allow_nsfw = { optional = false, type = "bool", default = false, example = false, options = [true,
false,
], explanation = "Whether to allow NSFW content, True or False" }
theme = { optional = false, default = "dark", example = "light", options = ["dark",
"light",
], explanation = "sets the Reddit theme, either LIGHT or DARK" }
times_to_run = { optional = false, default = 1, example = 2, explanation = "used if you want to run multiple times. set to an int e.g. 4 or 29 or 1", type = "int", nmin = 1, oob_error = "It's very hard to run something less than once." }
opacity = { optional = false, default = 0.9, example = 0.8, explanation = "Sets the opacity of the comments when overlayed over the background", type = "float", nmin = 0, nmax = 1, oob_error = "The opacity HAS to be between 0 and 1", input_error = "The opacity HAS to be a decimal number between 0 and 1" }
transition = { optional = true, default = 0.2, example = 0.2, explanation = "Sets the transition time (in seconds) between the comments. Set to 0 if you want to disable it.", type = "float", nmin = 0, nmax = 2, oob_error = "The transition HAS to be between 0 and 2", input_error = "The opacity HAS to be a decimal number between 0 and 2" }
storymode = { optional = true, type = "bool", default = false, example = false, options = [true,
false,
], explanation = "not yet implemented" }
[settings.allow_nsfw]
optional = false
var_type = "bool"
default = false
example = false
options = [true, false]
explanation = "Whether to allow NSFW content, True or False"
[settings.theme]
optional = false
default = "dark"
example = "light"
options = ["dark", "light"]
explanation = "sets the Reddit theme, either LIGHT or DARK"
[settings.times_to_run]
optional = false
default = 1
example = 2
explanation = "used if you want to run multiple times. set to an int e.g. 4 or 29 or 1"
var_type = "int"
nmin = 1
oob_error = "It's very hard to run something less than once."
[settings.opacity]
optional = false
default = 0.9
example = 0.8
explanation = "Sets the opacity of the comments when overlayed over the background"
var_type = "float"
nmin = 0
nmax = 1
oob_error = "The opacity HAS to be between 0 and 1"
input_error = "The opacity HAS to be a decimal number between 0 and 1"
[settings.transition]
optional = true
default = 0.2
example = 0.2
explanation = "Sets the transition time (in seconds) between the comments. Set to 0 if you want to disable it."
var_type = "float"
nmin = 0
nmax = 2
oob_error = "The transition HAS to be between 0 and 2"
input_error = "The opacity HAS to be a decimal number between 0 and 2"
[settings.storymode]
optional = true
var_type = "bool"
default = false
example = false
options = [true, false]
explanation = "not yet implemented"
[settings.background]
background_choice = { optional = true, default = "minecraft", example = "minecraft", options = ["minecraft", "gta", "rocket-league", "motor-gta", "csgo-surf", "cluster-truck", ""], explanation = "Sets the background for the video" }
#background_audio = { optional = true, type = "bool", default = false, example = false, options = [true,
# false,
#], explaination="Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)" }
#background_audio_volume = { optional = true, type = "float", default = 0.3, example = 0.1, explanation="Sets the volume of the background audio. only used if the background_audio is also set to true" }
[settings.background.background_choice]
optional = true
default = "minecraft"
example = "minecraft"
options = ["minecraft", "gta", "rocket-league", "motor-gta", "csgo-surf", "cluster-truck", ""]
explanation = "Sets the background for the video"
#[settings.background.background_audio]
#optional = true
#var_type = "bool"
#default = false
#example = false
#options = [true, false]
#explaination="Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)"
#
#[settings.background.background_audio_volume]
#optional = true
#var_type = "float"
#default = 0.3
#example = 0.1
#explanation="Sets the volume of the background audio. only used if the background_audio is also set to true"
[settings.tts]
choice = { optional = false, default = "", options = ["streamlabspolly", "tiktok", "googletranslate", "awspolly", ], example = "streamlabspolly", explanation = "The backend used for TTS generation. This can be left blank and you will be prompted to choose at runtime." }
aws_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for AWS Polly" }
streamlabs_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for Streamlabs Polly" }
tiktok_voice = { optional = false, default = "en_us_006", example = "en_us_006", explanation = "The voice used for TikTok TTS" }
[settings.tts.choice]
optional = false
default = ""
options = ["streamlabspolly", "tiktok", "googletranslate", "awspolly", ""]
example = "streamlabspolly"
explanation = "The backend used for TTS generation. This can be left blank and you will be prompted to choose at runtime."
[settings.tts.aws_polly_voice]
optional = false
default = "Matthew"
example = "Matthew"
explanation = "The voice used for AWS Polly"
[settings.tts.streamlabs_polly_voice]
optional = false
default = "Matthew"
example = "Matthew"
explanation = "The voice used for Streamlabs Polly"
[settings.tts.tiktok_voice]
optional = false
default = "en_us_006"
example = "en_us_006"
explanation = "The voice used for TikTok TTS"

@ -5,6 +5,7 @@ from rich.padding import Padding
from rich.panel import Panel
from rich.text import Text
from rich.columns import Columns
from typing import Optional, Union
import re
console = Console()
@ -36,18 +37,32 @@ def print_substep(text, style=""):
def handle_input(
message: str = "",
check_type=False,
match: str = "",
err_message: str = "",
nmin=None,
nmax=None,
oob_error="",
extra_info="",
options: list = None,
default=NotImplemented,
optional=False,
*,
var_type: Union[str, bool] = False,
regex: str = "",
input_error: str = "",
nmin: Optional[int] = None, # noqa
nmax: Optional[int] = None, # noqa
oob_error: str = "",
explanation: str = "",
options: list = None,
default: Optional[str] = NotImplemented,
optional: bool = False,
example: Optional[str] = None,
name: str = "",
message: Optional[Union[str, float]] = None,
):
if not message:
message = (
(("[blue]Example: " + str(example) + "\n") if example else "")
+ "[red]"
+ ("Non-optional ", "Optional ")[optional]
+ "[#C0CAF5 bold]"
+ str(name)
+ "[#F7768E bold]="
)
var_type: any = eval(var_type) if var_type else var_type
if optional:
console.print(message + "\n[green]This is an optional value. Do you want to skip it? (y/n)")
if input().casefold().startswith("y"):
@ -63,16 +78,16 @@ def handle_input(
if input().casefold().startswith("y"):
return default
if options is None:
match = re.compile(match)
console.print("[green bold]" + extra_info, no_wrap=True)
regex = re.compile(regex)
console.print("[green bold]" + explanation, no_wrap=True)
while True:
console.print(message, end="")
user_input = input("").strip()
if check_type is not False:
if var_type is not False:
try:
user_input = check_type(user_input)
user_input = var_type(user_input)
if (nmin is not None and user_input < nmin) or (
nmax is not None and user_input > nmax
nmax is not None and user_input > nmax
):
# FAILSTATE Input out of bounds
console.print("[red]" + oob_error)
@ -80,34 +95,34 @@ def handle_input(
break # Successful type conversion and number in bounds
except ValueError:
# Type conversion failed
console.print("[red]" + err_message)
console.print("[red]" + input_error)
continue
elif match != "" and re.match(match, user_input) is None:
console.print("[red]" + err_message + "\nAre you absolutely sure it's correct?(y/n)")
elif regex != "" and re.match(regex, user_input) is None:
console.print("[red]" + input_error + "\nAre you absolutely sure it's correct?(y/n)")
if input().casefold().startswith("y"):
break
continue
else:
# FAILSTATE Input STRING out of bounds
if (nmin is not None and len(user_input) < nmin) or (
nmax is not None and len(user_input) > nmax
nmax is not None and len(user_input) > nmax
):
console.print("[red bold]" + oob_error)
continue
break # SUCCESS Input STRING in bounds
return user_input
console.print(extra_info, no_wrap=True)
console.print(explanation, no_wrap=True)
while True:
console.print(message, end="")
user_input = input("").strip()
if check_type is not False:
if var_type is not False:
try:
isinstance(eval(user_input), check_type)
return check_type(user_input)
except:
isinstance(eval(user_input), var_type)
return var_type(user_input)
except Exception: # noqa (Exception is fine, it's not too broad)
console.print(
"[red bold]"
+ err_message
+ input_error
+ "\nValid options are: "
+ ", ".join(map(str, options))
+ "."
@ -116,5 +131,5 @@ def handle_input(
if user_input in options:
return user_input
console.print(
"[red bold]" + err_message + "\nValid options are: " + ", ".join(map(str, options)) + "."
"[red bold]" + input_error + "\nValid options are: " + ", ".join(map(str, options)) + "."
)

@ -1,170 +1,260 @@
#!/usr/bin/env python
import toml
from rich.console import Console
import re
from os.path import exists
from re import match
from typing import Optional, Union, TypeVar, Callable
from typing import Tuple, Dict
from utils.console import handle_input, console
from utils.console import handle_input
function = TypeVar("function", bound=Callable[..., object])
config: Optional[dict] = None
config_name: str = "config.toml"
config_template_name: str = "utils/.config.template.toml"
console = Console()
config = dict # autocomplete
def crawl(
obj: dict,
func: function = lambda x, y: print(x, y),
path: Optional[list] = None,
) -> None:
"""
Crawls on values of the dict and executes func if found dict w/ settings
def crawl(obj: dict, func=lambda x, y: print(x, y, end="\n"), path=None):
if path is None: # path Default argument value is mutable
Args:
obj: Dict to be crawled on
func: Function to be executed with settings
path: List with keys of nested dict
"""
if not path:
path = []
for key in obj.keys():
if type(obj[key]) is dict:
crawl(obj[key], func, path + [key])
for key, value in obj.items():
if type(value) is dict and any([type(v) is dict for v in value.values()]):
crawl(value, func, path + [key])
continue
func(path + [key], obj[key])
func(path + [key], value)
def check(
value: any,
checks: dict,
name: str,
) -> any:
"""
Checks values and asks user for input if value is incorrect
def check(value, checks, name):
def get_check_value(key, default_result):
return checks[key] if key in checks else default_result
Args:
value: Values to check
checks: List of checks as a dict
name: Name of the value to be checked
incorrect = False
if value == {}:
incorrect = True
if not incorrect and "type" in checks:
Returns:
Correct value
"""
correct = True if value else False
if correct and "type" in checks:
try:
value = eval(checks["type"])(value)
except:
incorrect = True
except Exception: # noqa (Exception is fine, it's not too broad)
correct = False
if (
not incorrect and "options" in checks and value not in checks["options"]
correct and "options" in checks and value not in checks["options"]
): # FAILSTATE Value is not one of the options
incorrect = True
correct = False
if (
not incorrect
correct
and "regex" in checks
and (
(isinstance(value, str) and re.match(checks["regex"], value) is None)
(isinstance(value, str) and match(checks["regex"], value) is None)
or not isinstance(value, str)
)
): # FAILSTATE Value doesn't match regex, or has regex but is not a string.
incorrect = True
correct = False
if (
not incorrect
correct
and not hasattr(value, "__iter__")
and (
("nmin" in checks and checks["nmin"] is not None and value < checks["nmin"])
or ("nmax" in checks and checks["nmax"] is not None and value > checks["nmax"])
)
):
incorrect = True
correct = False
if (
not incorrect
correct
and hasattr(value, "__iter__")
and (
("nmin" in checks and checks["nmin"] is not None and len(value) < checks["nmin"])
or ("nmax" in checks and checks["nmax"] is not None and len(value) > checks["nmax"])
)
):
incorrect = True
if incorrect:
value = handle_input(
message=(
(("[blue]Example: " + str(checks["example"]) + "\n") if "example" in checks else "")
+ "[red]"
+ ("Non-optional ", "Optional ")["optional" in checks and checks["optional"] is True]
)
+ "[#C0CAF5 bold]"
+ str(name)
+ "[#F7768E bold]=",
extra_info=get_check_value("explanation", ""),
check_type=eval(get_check_value("type", "False")),
default=get_check_value("default", NotImplemented),
match=get_check_value("regex", ""),
err_message=get_check_value("input_error", "Incorrect input"),
nmin=get_check_value("nmin", None),
nmax=get_check_value("nmax", None),
oob_error=get_check_value(
"oob_error", "Input out of bounds(Value too high/low/long/short)"
),
options=get_check_value("options", None),
optional=get_check_value("optional", False),
)
correct = False
if not correct:
default_values = {
"explanation": "",
"var_type": "False",
"default": NotImplemented,
"regex": "",
"input_error": "Incorrect input",
"nmin": None, # noqa
"nmax": None, # noqa
"oob_error": "Input out of bounds(Value too high/low/long/short)",
"options": None,
"optional": False,
}
[checks.update({key: value}) for key, value in default_values.items() if checks.get(key, 'Non') == 'Non']
value = handle_input(name=name, **checks)
return value
def crawl_and_check(obj: dict, path: list, checks: dict = {}, name=""):
if len(path) == 0:
return check(obj, checks, name)
if path[0] not in obj.keys():
obj[path[0]] = {}
obj[path[0]] = crawl_and_check(obj[path[0]], path[1:], checks, path[0])
def nested_get(
obj: dict,
keys: list,
) -> any:
"""
Gets value from nested dict by list with path
Args:
obj: Nested dict
keys: List with path
Return:
Value of last key
"""
for key in keys:
obj = obj.get(key, {})
return obj
def check_vars(path, checks):
global config
crawl_and_check(config, path, checks)
def nested_set(
obj: dict,
keys: list,
value: any,
) -> None:
"""
Sets last key in the nested dict by the path
Args:
obj: Nested dict
keys: List with path
value: Value to set
"""
for key in keys[:-1]:
obj = obj.setdefault(key, {})
obj[keys[-1]] = value
def check_vars(
path: list,
checks: dict,
) -> None:
"""
Checks if value is in nested dict and correct by path of keys
def check_toml(template_file, config_file) -> Tuple[bool, Dict]:
Args:
path: List with path
checks: Dict with all checks
"""
global config
config = None
try:
template = toml.load(template_file)
except Exception as error:
console.print(f"[red bold]Encountered error when trying to to load {template_file}: {error}")
return False
try:
config = toml.load(config_file)
except toml.TomlDecodeError:
console.print(
f"""[blue]Couldn't read {config_file}.
Overwrite it?(y/n)"""
)
if not input().startswith("y"):
print("Unable to read config, and not allowed to overwrite it. Giving up.")
if checks is None:
checks = dict()
value = check(
nested_get(config, path),
checks,
name=path[-1],
)
nested_set(config, path, value)
def check_config_wrapper(
func: function,
) -> function:
"""
Exception wrapper for check_config function
"""
def wrapper(*args, **kwargs):
if args:
kwargs["name"] = args[0]
if args.__len__() > 1:
kwargs["template_name"] = args[-1]
if not kwargs or not all(arg is not None for arg in kwargs.values()):
kwargs["name"] = config_name
kwargs["template_name"] = config_template_name
try:
return func(*args, **kwargs)
except toml.TomlDecodeError:
if console.input(f"[blue]Couldn't read {kwargs['name']}.\nOverwrite it?(y/n)").startswith("y"):
try:
with open(kwargs["name"], "w") as f:
f.write("")
return func(*args, **kwargs)
except Exception: # noqa (Exception is fine, it's not too broad)
console.print(
f"[red bold]Failed to overwrite {kwargs['name']}. Giving up.\n"
f"Suggestion: check {kwargs['name']} permissions for the user."
)
return False
console.print("Unable to read config, and not allowed to overwrite it. Giving up.")
return False
else:
try:
with open(config_file, "w") as f:
f.write("")
except:
console.print(
f"[red bold]Failed to overwrite {config_file}. Giving up.\nSuggestion: check {config_file} permissions for the user."
)
return False
except FileNotFoundError:
console.print(
f"""[blue]Couldn't find {config_file}
Creating it now."""
)
# except Exception as error:
# console.print(f"[red bold]Encountered error when trying to to load {kwargs['template_name']}: {error}")
# return False
return wrapper
@check_config_wrapper
def check_config(
name: Optional[str] = None,
template_name: Optional[str] = None,
) -> Union[dict, bool]:
"""
Checks config and returns corrected version
Args:
name: Config name
template_name: Template name
Return:
Corrected config file as a dict
"""
if not name:
name = config_name
if not template_name:
template_name = config_template_name
global config
if not exists(name):
console.print(f"[blue]Couldn't find {name}\nCreating it now.")
try:
with open(config_file, "x") as f:
with open(name, "w+") as f:
f.write("")
config = {}
except:
config = dict()
except Exception: # noqa (Exception is fine, it's not too broad)
console.print(
f"[red bold]Failed to write to {config_file}. Giving up.\nSuggestion: check the folder's permissions for the user."
f"[red bold]Failed to write to {name}.Giving up.\n"
f"Suggestion: check the folder's permissions for the user."
)
return False
else:
config = toml.load(config_name)
console.print(
"""\
[blue bold]###############################
# #
# Checking TOML configuration #
# #
###############################
If you see any prompts, that means that you have unset/incorrectly set variables, please input the correct values.\
"""
)
template = toml.load(template_name)
crawl(template, check_vars)
with open(config_file, "w") as f:
with open(config_name, "w") as f:
toml.dump(config, f)
return config
if __name__ == "__main__":
check_toml("utils/.config.template.toml", "config.toml")
print(check_config())
# template = toml.load(config_template_name)
# crawl(template)

Loading…
Cancel
Save