mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-05 20:28:27 +02:00
implemented macros to make safe_eval expression more readable.
This commit is contained in:
parent
62a2e31ce2
commit
5ea1d87974
3 changed files with 41 additions and 9 deletions
|
@ -27,7 +27,7 @@ class ModConfig:
|
|||
__slots__ = ("name", "path", "nickname", "variant", "tags_prefix", "tags_suffix",
|
||||
"default_track", "_tracks", "version", "original_track_prefix", "swap_original_order",
|
||||
"keep_original_track", "enable_random_cup", "tags_cups", "track_file_template",
|
||||
"multiplayer_disable_if")
|
||||
"multiplayer_disable_if", "macros")
|
||||
|
||||
def __init__(self, path: Path | str, name: str, nickname: str = None, version: str = None, variant: str = None,
|
||||
tags_prefix: dict[Tag, str] = None, tags_suffix: dict[Tag, str] = None,
|
||||
|
@ -35,9 +35,10 @@ class ModConfig:
|
|||
default_track: "Track | TrackGroup" = None, tracks: list["Track | TrackGroup"] = None,
|
||||
original_track_prefix: bool = None, swap_original_order: bool = None,
|
||||
keep_original_track: bool = None, enable_random_cup: bool = None,
|
||||
track_file_template: str = None, multiplayer_disable_if: str = None):
|
||||
track_file_template: str = None, multiplayer_disable_if: str = None, macros: dict | None = None):
|
||||
|
||||
self.path = Path(path)
|
||||
self.macros: dict = macros if macros is not None else {}
|
||||
|
||||
self.name: str = name
|
||||
self.nickname: str = nickname if nickname is not None else name
|
||||
|
@ -63,17 +64,18 @@ class ModConfig:
|
|||
return f"<ModConfig name={self.name} version={self.version}>"
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, path: Path | str, config_dict: dict) -> "ModConfig":
|
||||
def from_dict(cls, path: Path | str, config_dict: dict, macros: dict | None) -> "ModConfig":
|
||||
"""
|
||||
Create a ModConfig from a dict
|
||||
:param path: path of the mod_config.json
|
||||
:param config_dict: dict containing the configuration
|
||||
:param macros: macro that can be used for safe_eval
|
||||
:return: ModConfig
|
||||
"""
|
||||
kwargs = {
|
||||
attr: config_dict.get(attr)
|
||||
for attr in cls.__slots__
|
||||
if attr not in ["name", "default_track", "_tracks", "tracks", "path"]
|
||||
if attr not in ["name", "default_track", "_tracks", "tracks", "path", "macros"]
|
||||
# these keys are treated after or are reserved
|
||||
}
|
||||
|
||||
|
@ -85,6 +87,7 @@ class ModConfig:
|
|||
|
||||
default_track=Track.from_dict(config_dict.get("default_track", {})),
|
||||
tracks=[Track.from_dict(track) for track in config_dict.get("tracks", [])],
|
||||
macros=macros,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -95,9 +98,12 @@ class ModConfig:
|
|||
:return: ModConfig
|
||||
"""
|
||||
config_file = Path(config_file)
|
||||
macros_file = config_file.parent / "macros.json"
|
||||
|
||||
return cls.from_dict(
|
||||
path=config_file,
|
||||
config_dict=json.loads(config_file.read_text(encoding="utf8"))
|
||||
config_dict=json.loads(config_file.read_text(encoding="utf8")),
|
||||
macros=json.loads(macros_file.read_text(encoding="utf8")) if macros_file.exists() else None,
|
||||
)
|
||||
|
||||
def get_mod_directory(self) -> Path:
|
||||
|
|
|
@ -28,6 +28,7 @@ class Patch:
|
|||
return (multiple_safe_eval if multiple else safe_eval)(
|
||||
template,
|
||||
env={"mod_config": self.mod_config} | (env if env is not None else {}),
|
||||
macros=self.mod_config.macros,
|
||||
)
|
||||
|
||||
def install(self, extracted_game: "ExtractedGame") -> Generator[dict, None, None]:
|
||||
|
|
|
@ -17,11 +17,17 @@ common_token_map = { # these operators and function are considered safe to use
|
|||
}
|
||||
|
||||
TOKEN_START, TOKEN_END = "{{", "}}"
|
||||
MACRO_START, MACRO_END = "##", "##"
|
||||
|
||||
|
||||
class TemplateParsingError(Exception):
|
||||
def __init__(self, token: str):
|
||||
super().__init__(f"Invalid token while parsing track representation:\n{token}")
|
||||
super().__init__(f"Invalid token while parsing safe_eval:\n{token}")
|
||||
|
||||
|
||||
class NotImplementedMacro(Exception):
|
||||
def __init__(self, macro: str):
|
||||
super().__init__(f"Invalid macro while parsing macros:\n{macro}")
|
||||
|
||||
|
||||
class SafeFunction:
|
||||
|
@ -51,14 +57,33 @@ class SafeFunction:
|
|||
return attr
|
||||
|
||||
|
||||
def safe_eval(template: str, env: dict[str, any] = None) -> str:
|
||||
def replace_macro(template: str, macros: dict[str, str]) -> str:
|
||||
"""
|
||||
Replace all the macro defined in macro by their respective value
|
||||
:param template: template where to replace the macro
|
||||
:param macros: dictionary associating macro with their replacement
|
||||
:return: the template with macro replaced
|
||||
"""
|
||||
|
||||
def format_macro(match: re.Match) -> str:
|
||||
if (macro := macros.get(match.group(1).strip())) is None: raise NotImplementedMacro(macro)
|
||||
return macro
|
||||
|
||||
# match everything between MACRO_START and MACRO_END.
|
||||
return re.sub(rf"{MACRO_START}(.*?){MACRO_END}", format_macro, template)
|
||||
|
||||
|
||||
def safe_eval(template: str, env: dict[str, any] = None, macros: dict[str, str] = None) -> str:
|
||||
"""
|
||||
Evaluate the template and return the result in a safe way
|
||||
:param env: variables to use when using eval
|
||||
:param template: template to evaluate
|
||||
:param macros: additionnal macro to replace in the template
|
||||
"""
|
||||
if env is None: env = {}
|
||||
if macros is None: macros = {}
|
||||
|
||||
template = replace_macro(template, macros)
|
||||
token_map: dict[str, str] = common_token_map | {var: var for var in env}
|
||||
final_token: str = ""
|
||||
|
||||
|
@ -111,7 +136,7 @@ def safe_eval(template: str, env: dict[str, any] = None) -> str:
|
|||
else: return final_token
|
||||
|
||||
|
||||
def multiple_safe_eval(template: str, env: dict[str, any] = None) -> str:
|
||||
def multiple_safe_eval(template: str, env: dict[str, any] = None, macros: dict[str, str] = None) -> str:
|
||||
def format_part_template(match: re.Match) -> str:
|
||||
"""
|
||||
when a token is found, replace it by the corresponding value
|
||||
|
@ -120,7 +145,7 @@ def multiple_safe_eval(template: str, env: dict[str, any] = None) -> str:
|
|||
"""
|
||||
# get the token string without the brackets, then strip it. Also double antislash
|
||||
part_template = match.group(1).strip().replace("\\", "\\\\")
|
||||
return safe_eval(part_template, env)
|
||||
return safe_eval(part_template, env, macros)
|
||||
|
||||
# pass everything between TOKEN_START and TOKEN_END in the function
|
||||
return re.sub(rf"{TOKEN_START}(.*?){TOKEN_END}", format_part_template, template)
|
||||
|
|
Loading…
Reference in a new issue