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__": 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() config is False and exit()
try: try:
if config["settings"]["times_to_run"]: if config["settings"]["times_to_run"]:

@ -1,46 +1,203 @@
[reddit]
[reddit.creds] [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_id]
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." } optional = false
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" } nmin = 12
password = { optional = false, nmin = 8, explanation = "the password of your reddit account", example = "fFAGRNJru1FTz70BzhT3Zg", oob_error = "Password too short" } nmax = 30
2fa = { optional = true, type = "bool", options = [true, explanation = "the ID of your Reddit app of SCRIPT type"
false, example = "fFAGRNJru1FTz70BzhT3Zg"
], default = false, explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False", example = true } 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] [reddit.thread]
random = { optional = true, options = [true, [reddit.thread.random]
false, optional = true
], 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" } options = [true, false]
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" } default = false
post_id = { optional = true, default = "", regex = "^((?!://|://)[+a-zA-Z0-9])*$", explanation = "Used if you want to use a specific post.", example = "urdtfx" } var_type = "bool"
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" } explanation = "If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: 'False'"
post_lang = { default = "", optional = true, explanation = "The language you would like to translate to.", example = "es-cr" } example = "True"
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.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] [settings]
allow_nsfw = { optional = false, type = "bool", default = false, example = false, options = [true, [settings.allow_nsfw]
false, optional = false
], explanation = "Whether to allow NSFW content, True or False" } var_type = "bool"
theme = { optional = false, default = "dark", example = "light", options = ["dark", default = false
"light", example = false
], explanation = "sets the Reddit theme, either LIGHT or DARK" } options = [true, false]
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." } explanation = "Whether to allow NSFW content, True or False"
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" } [settings.theme]
storymode = { optional = true, type = "bool", default = false, example = false, options = [true, optional = false
false, default = "dark"
], explanation = "not yet implemented" } 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] [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_choice]
#background_audio = { optional = true, type = "bool", default = false, example = false, options = [true, optional = true
# false, default = "minecraft"
#], explaination="Sets a audio to play in the background (put a background.mp3 file in the assets/backgrounds directory for it to be used.)" } example = "minecraft"
#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" } 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] [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.choice]
aws_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for AWS Polly" } optional = false
streamlabs_polly_voice = { optional = false, default = "Matthew", example = "Matthew", explanation = "The voice used for Streamlabs Polly" } default = ""
tiktok_voice = { optional = false, default = "en_us_006", example = "en_us_006", explanation = "The voice used for TikTok TTS" } 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.panel import Panel
from rich.text import Text from rich.text import Text
from rich.columns import Columns from rich.columns import Columns
from typing import Optional, Union
import re import re
console = Console() console = Console()
@ -36,18 +37,32 @@ def print_substep(text, style=""):
def handle_input( def handle_input(
message: str = "", *,
check_type=False, var_type: Union[str, bool] = False,
match: str = "", regex: str = "",
err_message: str = "", input_error: str = "",
nmin=None, nmin: Optional[int] = None, # noqa
nmax=None, nmax: Optional[int] = None, # noqa
oob_error="", oob_error: str = "",
extra_info="", explanation: str = "",
options: list = None, options: list = None,
default=NotImplemented, default: Optional[str] = NotImplemented,
optional=False, 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: if optional:
console.print(message + "\n[green]This is an optional value. Do you want to skip it? (y/n)") console.print(message + "\n[green]This is an optional value. Do you want to skip it? (y/n)")
if input().casefold().startswith("y"): if input().casefold().startswith("y"):
@ -63,14 +78,14 @@ def handle_input(
if input().casefold().startswith("y"): if input().casefold().startswith("y"):
return default return default
if options is None: if options is None:
match = re.compile(match) regex = re.compile(regex)
console.print("[green bold]" + extra_info, no_wrap=True) console.print("[green bold]" + explanation, no_wrap=True)
while True: while True:
console.print(message, end="") console.print(message, end="")
user_input = input("").strip() user_input = input("").strip()
if check_type is not False: if var_type is not False:
try: try:
user_input = check_type(user_input) user_input = var_type(user_input)
if (nmin is not None and user_input < nmin) or ( 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
): ):
@ -80,10 +95,10 @@ def handle_input(
break # Successful type conversion and number in bounds break # Successful type conversion and number in bounds
except ValueError: except ValueError:
# Type conversion failed # Type conversion failed
console.print("[red]" + err_message) console.print("[red]" + input_error)
continue continue
elif match != "" and re.match(match, user_input) is None: elif regex != "" and re.match(regex, user_input) is None:
console.print("[red]" + err_message + "\nAre you absolutely sure it's correct?(y/n)") console.print("[red]" + input_error + "\nAre you absolutely sure it's correct?(y/n)")
if input().casefold().startswith("y"): if input().casefold().startswith("y"):
break break
continue continue
@ -96,18 +111,18 @@ def handle_input(
continue continue
break # SUCCESS Input STRING in bounds break # SUCCESS Input STRING in bounds
return user_input return user_input
console.print(extra_info, no_wrap=True) console.print(explanation, no_wrap=True)
while True: while True:
console.print(message, end="") console.print(message, end="")
user_input = input("").strip() user_input = input("").strip()
if check_type is not False: if var_type is not False:
try: try:
isinstance(eval(user_input), check_type) isinstance(eval(user_input), var_type)
return check_type(user_input) return var_type(user_input)
except: except Exception: # noqa (Exception is fine, it's not too broad)
console.print( console.print(
"[red bold]" "[red bold]"
+ err_message + input_error
+ "\nValid options are: " + "\nValid options are: "
+ ", ".join(map(str, options)) + ", ".join(map(str, options))
+ "." + "."
@ -116,5 +131,5 @@ def handle_input(
if user_input in options: if user_input in options:
return user_input return user_input
console.print( 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 #!/usr/bin/env python
import toml import toml
from rich.console import Console from os.path import exists
import re 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): Args:
if path is None: # path Default argument value is mutable obj: Dict to be crawled on
func: Function to be executed with settings
path: List with keys of nested dict
"""
if not path:
path = [] path = []
for key in obj.keys():
if type(obj[key]) is dict: for key, value in obj.items():
crawl(obj[key], func, path + [key]) if type(value) is dict and any([type(v) is dict for v in value.values()]):
crawl(value, func, path + [key])
continue continue
func(path + [key], obj[key]) func(path + [key], value)
def check(value, checks, name): def check(
def get_check_value(key, default_result): value: any,
return checks[key] if key in checks else default_result checks: dict,
name: str,
) -> any:
"""
Checks values and asks user for input if value is incorrect
Args:
value: Values to check
checks: List of checks as a dict
name: Name of the value to be checked
Returns:
Correct value
"""
correct = True if value else False
incorrect = False if correct and "type" in checks:
if value == {}:
incorrect = True
if not incorrect and "type" in checks:
try: try:
value = eval(checks["type"])(value) value = eval(checks["type"])(value)
except: except Exception: # noqa (Exception is fine, it's not too broad)
incorrect = True correct = False
if ( 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 ): # FAILSTATE Value is not one of the options
incorrect = True correct = False
if ( if (
not incorrect correct
and "regex" in checks and "regex" in checks
and ( 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) or not isinstance(value, str)
) )
): # FAILSTATE Value doesn't match regex, or has regex but is not a string. ): # FAILSTATE Value doesn't match regex, or has regex but is not a string.
incorrect = True correct = False
if ( if (
not incorrect correct
and not hasattr(value, "__iter__") and not hasattr(value, "__iter__")
and ( and (
("nmin" in checks and checks["nmin"] is not None and value < checks["nmin"]) ("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"]) or ("nmax" in checks and checks["nmax"] is not None and value > checks["nmax"])
) )
): ):
incorrect = True correct = False
if ( if (
not incorrect correct
and hasattr(value, "__iter__") and hasattr(value, "__iter__")
and ( and (
("nmin" in checks and checks["nmin"] is not None and len(value) < checks["nmin"]) ("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"]) or ("nmax" in checks and checks["nmax"] is not None and len(value) > checks["nmax"])
) )
): ):
incorrect = True correct = False
if incorrect: if not correct:
value = handle_input( default_values = {
message=( "explanation": "",
(("[blue]Example: " + str(checks["example"]) + "\n") if "example" in checks else "") "var_type": "False",
+ "[red]" "default": NotImplemented,
+ ("Non-optional ", "Optional ")["optional" in checks and checks["optional"] is True] "regex": "",
) "input_error": "Incorrect input",
+ "[#C0CAF5 bold]" "nmin": None, # noqa
+ str(name) "nmax": None, # noqa
+ "[#F7768E bold]=", "oob_error": "Input out of bounds(Value too high/low/long/short)",
extra_info=get_check_value("explanation", ""), "options": None,
check_type=eval(get_check_value("type", "False")), "optional": False,
default=get_check_value("default", NotImplemented), }
match=get_check_value("regex", ""),
err_message=get_check_value("input_error", "Incorrect input"), [checks.update({key: value}) for key, value in default_values.items() if checks.get(key, 'Non') == 'Non']
nmin=get_check_value("nmin", None),
nmax=get_check_value("nmax", None), value = handle_input(name=name, **checks)
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),
)
return value return value
def crawl_and_check(obj: dict, path: list, checks: dict = {}, name=""): def nested_get(
if len(path) == 0: obj: dict,
return check(obj, checks, name) keys: list,
if path[0] not in obj.keys(): ) -> any:
obj[path[0]] = {} """
obj[path[0]] = crawl_and_check(obj[path[0]], path[1:], checks, path[0]) 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 return obj
def check_vars(path, checks): def nested_set(
global config obj: dict,
crawl_and_check(config, path, checks) 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_toml(template_file, config_file) -> Tuple[bool, Dict]: def check_vars(
path: list,
checks: dict,
) -> None:
"""
Checks if value is in nested dict and correct by path of keys
Args:
path: List with path
checks: Dict with all checks
"""
global config global config
config = None if checks is None:
try: checks = dict()
template = toml.load(template_file)
except Exception as error: value = check(
console.print(f"[red bold]Encountered error when trying to to load {template_file}: {error}") nested_get(config, path),
return False 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: try:
config = toml.load(config_file) return func(*args, **kwargs)
except toml.TomlDecodeError: except toml.TomlDecodeError:
console.print( if console.input(f"[blue]Couldn't read {kwargs['name']}.\nOverwrite it?(y/n)").startswith("y"):
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.")
return False
else:
try: try:
with open(config_file, "w") as f: with open(kwargs["name"], "w") as f:
f.write("") f.write("")
except: return func(*args, **kwargs)
except Exception: # noqa (Exception is fine, it's not too broad)
console.print( console.print(
f"[red bold]Failed to overwrite {config_file}. Giving up.\nSuggestion: check {config_file} permissions for the user." f"[red bold]Failed to overwrite {kwargs['name']}. Giving up.\n"
f"Suggestion: check {kwargs['name']} permissions for the user."
) )
return False return False
except FileNotFoundError: console.print("Unable to read config, and not allowed to overwrite it. Giving up.")
console.print( return False
f"""[blue]Couldn't find {config_file} # except Exception as error:
Creating it now.""" # 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: try:
with open(config_file, "x") as f: with open(name, "w+") as f:
f.write("") f.write("")
config = {} config = dict()
except: except Exception: # noqa (Exception is fine, it's not too broad)
console.print( 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 return False
else:
config = toml.load(config_name)
console.print( template = toml.load(template_name)
"""\
[blue bold]###############################
# #
# Checking TOML configuration #
# #
###############################
If you see any prompts, that means that you have unset/incorrectly set variables, please input the correct values.\
"""
)
crawl(template, check_vars) crawl(template, check_vars)
with open(config_file, "w") as f: with open(config_name, "w") as f:
toml.dump(config, f) toml.dump(config, f)
return config return config
if __name__ == "__main__": 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