Source code for wow_acc.model

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

"""
本模块定义了 :class:`Account`, :class:`Realm`, :class:`Character` 三个配置应用场景类.

.. note::

    注意, 这里的类都带有一个 ``def new()`` 的工厂函数, 请不要直接用构造器, 而用这个工厂函数.
    里面会自动把新建的对象加入到父对象的映射中去.
"""

import typing as T
import dataclasses
from pathlib import Path
from functools import cached_property
from .vendor.better_dataclasses import DataClass, T_DATA

import yaml


[docs]def right_zfill(s: str, length: int) -> str: """ fill character "0" to the right end to ensure a string has a fixed length. """ return s + "0" * (length - len(s))
[docs]def to_var_name(s: str) -> str: """ Convert a string to a valid variable name. """ return s.replace("-", "_").replace(" ", "_")
[docs]@dataclasses.dataclass(frozen=True, eq=True, order=True) class Account(DataClass): """ 代表着一个具体账号. 是可哈希, 可排序, 可用集合去重的. :param account: 账号名. :param realms: 该账号下的所有 :class:`Realm` 对象的列表. """ account: str = dataclasses.field() realms: T.List["Realm"] = dataclasses.field(default_factory=list) @property def sort_key(self) -> str: """ 创建账号的排序键. 本质是左对齐并在尾部添加 "0" 的字符串. """ return right_zfill(self.account, 16) def __hash__(self): """ 判断账号是否相同的哈希函数. """ return hash(self.sort_key) @property def var_name(self) -> str: """ Variable name for generated Python module. """ return to_var_name(self.account) @cached_property def realms_mapper(self) -> T.Dict[str, "Realm"]: """ 返回该账号下的所有服务器从名字到对象的映射. """ return {realm.realm: realm for realm in self.realms} @cached_property def characters(self) -> T.List["Character"]: """ 返回该账号下所有服务器上的所有角色. """ chars = list() for realm in self.realms: for character in realm.characters: chars.append(character) return chars @property def wtf_account_name(self) -> str: r""" 返回账号名的全部大写形式. 用于 WTF 文件夹中的路径名. 例如: ``C:\...\WTF\Account\MYACCOUNT\...`` """ return self.account.upper()
[docs]@dataclasses.dataclass(frozen=True, eq=True, order=True) class Realm(DataClass): """ 代表着一个具体账号下的具体的服务器. 是可哈希, 可排序, 可用集合去重的. :param account: 该服务器所属的 :class:`Account` 对象. :param realm: 服务器名. :param characters: 该服务器下的所有 :class:`Character` 的列表. """ account: Account = dataclasses.field() realm: str = dataclasses.field() characters: T.List["Character"] = dataclasses.field(default_factory=list) @property def sort_key(self) -> str: """ 服务器名排序键. 本质是先对账号排序, 再对服务器名排序. 和账号的排序键一样, 也是左对齐 并在尾部添加 "0" 的字符串. """ return ".".join( [ self.account.sort_key, right_zfill(self.realm, 16), ] ) def __hash__(self): """ 判断服务器是否相同的哈希函数. """ return hash(self.sort_key) @property def var_name(self) -> str: """ Variable name for generated Python module. """ return f"{self.account.var_name}_{to_var_name(self.realm)}" @property def account_name(self) -> str: """ 该服务器所属的账号名. """ return self.account.account @property def characters_mapper(self) -> T.Dict[str, "Character"]: """ 返回该账号下的所有游戏角色从名字到对象的映射. """ return {char.character: char for char in self.characters}
[docs]@dataclasses.dataclass(frozen=True, eq=True, order=True) class Character(DataClass): """ 代表着一个具体账号下的具体的服务器上的具体游戏角色. 是可哈希, 可排序, 可用集合去重的. :param realm: 该角色所属的 :class:`Realm` 对象. :param character: 角色名. """ realm: Realm = dataclasses.field() character: str = dataclasses.field() @property def sort_key(self) -> str: """ 角色名排序键. 本质是先对服务器排序, 再对角色名排序. 和服务器的排序键一样, 也是左对齐 并在尾部添加 "0" 的字符串. """ return ".".join( [ self.realm.sort_key, right_zfill(self.character, 16), ] ) def __hash__(self): """ 判断角色是否相同的哈希函数. """ return hash(self.sort_key) @property def var_name(self) -> str: """ Variable name for generated Python module. """ return f"{self.realm.var_name}_{to_var_name(self.character)}" @property def realm_name(self) -> str: """ 该角色所属的服务器名. """ return self.realm.realm @property def account(self) -> Account: """ 该角色所属的账号对象. """ return self.realm.account @property def account_name(self) -> str: """ 该角色所属的账号名. """ return self.account.account @property def titled_character_name(self) -> str: r""" 角色名的首字母大写形式. 例如 "mycharacter" -> "Mycharacter". 用于 WTF 文件夹中的路径名. 例如: ``C:\...\WTF\Account\MYACCOUNT\MyServer\Mycharacter\...`` """ return self.character[0].upper() + self.character[1:].lower()
[docs]@dataclasses.dataclass(frozen=True) class Dataset(DataClass): """ 代表着一堆 :class:`Account`, :class:`Realm`, :class:`Character` 的集合. """ accounts: T.Dict[str, Account] = dataclasses.field(default_factory=dict)
[docs] @classmethod def from_yaml(cls, stream: T.Union[str, Path]): """ Sample YAML file format: .. code-block:: yaml acc1: realm1: - char111 - char112 realm2: - char121 - char122 acc2: realm1: - char211 - char212 realm2: - char221 - char222 """ if isinstance(stream, str): # pragma: no cover data = yaml.load(stream, Loader=yaml.SafeLoader) elif isinstance(stream, Path): with stream.open() as f: data = yaml.load(f, Loader=yaml.SafeLoader) else: # pragma: no cover raise TypeError accounts = dict() for account_name, account_data in data.items(): account = Account(account=account_name) for realm_name, character_name_list in account_data.items(): realm = Realm(account=account, realm=realm_name) for character_name in character_name_list: character = Character(realm=realm, character=character_name) realm.characters.append(character) account.realms.append(realm) accounts[account.account] = account return cls(accounts=accounts)
[docs] def to_module( self, import_line: str, dataset_var_name: str = "ds", ) -> str: """ Generate python code that can be used to enumerate this dataset. :param import_line: this line will be used to import the :class:`Dataset` object, usually it is ``from .dataset import ds`` :param dataset_var_name: the variable name of the :class:`Dataset` object, usually it is ``ds``. """ lines = [ "# -*- coding: utf-8 -*-", "", import_line, "", "", "# fmt: off", "class AccountEnum:", ] tab = " " * 4 for account_name, account in self.accounts.items(): lines.append( f'{tab}{account.var_name} = {dataset_var_name}.accounts["{account_name}"]' ) lines.append("") lines.append("") lines.append("class RealmEnum:") for account_name, account in self.accounts.items(): for realm_name, realm in account.realms_mapper.items(): lines.append( f'{tab}{realm.var_name} = {dataset_var_name}.accounts["{account_name}"].realms_mapper["{realm_name}"]' ) lines.append("") lines.append("") lines.append("class CharacterEnum:") for account_name, account in self.accounts.items(): for realm_name, realm in account.realms_mapper.items(): for character_name, character in realm.characters_mapper.items(): lines.append( f'{tab}{character.var_name} = {dataset_var_name}.accounts["{account_name}"].realms_mapper["{realm_name}"].characters_mapper["{character_name}"]' ) lines.append("# fmt: on") return "\n".join(lines)