Source code for wow_sdm.exp03_wotlk.model

# -*- coding: utf-8 -*-

"""
为了能实现用 Python 来操作 SDM 插件的 ``SavedVariables/SuperDuperMacro.lua`` 文件,
我们要对 .lua 文件中的代码块进行数据建模, 定义一些 Python 类来代表这些代码块, 然后实现将
Python 对象转化为 .lua 代码块的逻辑.
"""

import typing as T
import dataclasses

from jinja2 import Template
from pathlib_mate import Path
from yaml import load, SafeLoader

from ..utils import apply


_dir_here = Path.dir_here(__file__)
_path_sdm_template = _dir_here / "sdm.tpl"
_sdm_template = Template(_path_sdm_template.read_text(encoding="utf-8"))


[docs]@dataclasses.dataclass class SdmCharacter: """ 代表 Character Macro 专有宏命令中关于角色信息的部分. 对应着如下代码块: .. code-block:: lua ["character"] = { ["name"] = "charname", ["realm"] = "realmname", }, """ name: str = dataclasses.field() realm: str = dataclasses.field()
[docs]class SdmMacroTypeEnum: """ 枚举 SDM 宏命令的三种类型. """ button = "b" floating = "f" script = "s"
_DEFAULT_TYPE = SdmMacroTypeEnum.button # 默认的宏命令类型 _DEFAULT_ID = 0 # 默认的起始 ID _DEFAULT_ICON = 1 # 默认的宏命令图标
[docs]@dataclasses.dataclass class SdmMacro: """ 定义了一个魔兽世界中的 SDM 宏命令的抽象, 目前只支持 Button + Global 这一种模式. 代表着如下代码块: .. code-block:: lua { ["type"] = "f", ["name"] = "macroname", ["character"] = { ["name"] = "charname", ["realm"] = "realmname", }, ["ID"] = 1, ["text"] = "/s hello", ["icon"] = 1, }, -- [1] """ name: str = dataclasses.field() character: T.Optional[SdmCharacter] = dataclasses.field(default=None) type: str = dataclasses.field(default=_DEFAULT_TYPE) id: int = dataclasses.field(default=_DEFAULT_ID) # SDM macro ID starts from 0 icon: int = dataclasses.field(default=_DEFAULT_ICON) # 1 is the Question Mark Icon text: str = dataclasses.field(default="")
[docs] def set_id(self, id: int) -> "SdmMacro": # pragma: no cover """ Update it's attributes value. """ self.id = id return self
[docs] def set_char(self, name: str, realm: str) -> "SdmMacro": # pragma: no cover """ Update it's attributes value. """ if self.character is None: self.character = SdmCharacter( name=name, realm=realm, ) else: self.character.name = name self.character.realm = realm return self
[docs] def is_global(self) -> bool: """ Is this SDM macro a global macro or character macro """ if self.character is None: # pragma: no cover return True elif (self.character.name is None) or (self.character.realm is None): return True else: return False
[docs] def encode_text(self) -> str: """ Encode macro text to single-ling Lua string. The final string of the macro body in lua has to have only one line. """ return self.text.replace("\n", "\\n")
[docs] @classmethod def from_yaml(cls, stream) -> "SdmMacro": """ 从人类可读写的 yaml 文件中读取数据, 创建 :class:`SDMMacro` 对象. 这是我们 这个模块的最核心的方法, 也是能让我们用 yaml 文件来维护宏命令的关键. 下面是一个示例的 yaml 文件. .. code-block:: yaml name: interrupt character: name: realm: type: b id: # you can find icon id on https://wotlk.evowow.com/?icons icon: description: | cancel casting spell, interrupt enemy casting immediately! text: | #showtooltip /stopcasting /cast Counterspell :param stream: 可以是 yaml 文件的字符串内容, 也可是 yaml 的 Path 对象, 也可以是 file object 对象. """ if isinstance(stream, str): # pragma: no cover data = load(stream, SafeLoader) elif isinstance(stream, Path): with stream.open("r", encoding="utf-8") as f: data = load(f, SafeLoader) else: # pragma: no cover data = load(stream, SafeLoader) return cls( name=data["name"], character=SdmCharacter(**data["character"]), type=data["type"] if data["type"] else _DEFAULT_TYPE, id=data["id"] if data["id"] else _DEFAULT_ID, icon=data["icon"] if data["icon"] else _DEFAULT_ICON, text=data["text"].strip(), )
[docs] def render(self) -> str: """ Render the corresponding SuperDupeMacro.lua code. See example at :class:`SDMMacro`. """ lines: T.List[str] = list() lines.append(f"[{self.id}] = {{") lines.append(f' ["type"] = "{self.type}",') lines.append(f' ["name"] = "{self.name}",') if self.is_global() is False: lines.append(f' ["character"] = {{') lines.append(f' ["name"] = "{self.character.name}",') lines.append(f' ["realm"] = "{self.character.realm}",') lines.append(f" }},") lines.append(f' ["ID"] = {self.id},') lines.append(f' ["icon"] = {self.icon},') lines.append(f' ["text"] = "{self.encode_text()}",') lines.append(f"}},") return "\n".join(lines)
[docs]@dataclasses.dataclass class SdmLua: """ 代表了 ``SuperDupeMacro.lua`` 文件的抽象. 该类只能用于将数据写入到 ``SuperDupeMacro.lua``, 而不能从 ``SuperDupeMacro.lua`` 中读取数据. :param path_lua: ``SuperDupeMacro.lua`` 文件路径. :param macros: :class:`SDMMacro` 对象的列表. """ path_lua: Path = dataclasses.field() macros: T.List[SdmMacro] = dataclasses.field(default_factory=list) def __post_init__(self): self.check_path_lua() self.check_macros()
[docs] def check_path_lua(self): """ 检查 :attr:`SdmLua.path_lua` 是否是 SuperDupeMacro.lua 文件. """ if self.path_lua.basename != "SuperDuperMacro.lua": # pragma: no cover raise ValueError(f"the SDMLua.path_lua has to end with SuperDupeMacro.lua!")
[docs] def check_macros(self): """ 检查 :attr:`SdmLua.macros` 中是否有重复的 macro id. """ id_set = {macro.id for macro in self.macros} if len(id_set) != len(self.macros): # pragma: no cover macro_id_list = [macro.id for macro in self.macros] raise ValueError( f"Cannot render SDM lua! Found duplicate id in 'macro_list': {macro_id_list}" )
[docs] def render(self) -> str: """ 将一堆 :class:`SDMMacro` 对象渲染成 SuperDupeMacro.lua 文件的内容 (只是生成内容 而不将内容写入文件). 这里面会检查 macro_list 中的 macro id 是否有重复, 如果有重复, 则会抛出异常. """ return _sdm_template.render(macros=self.macros)
def write( self, real_run: bool = True, verbose: bool = False, ) -> str: # pragma: no cover content = self.render() apply( path=self.path_lua, content=content, real_run=real_run, verbose=verbose, ) return content