cls.config

cls/config2.py

  1"""
  2cls/config2.py
  3"""
  4
  5import logging
  6import os
  7import sys
  8from configparser import ConfigParser, SectionProxy
  9from dataclasses import dataclass, field
 10from itertools import chain
 11from math import ceil
 12from pathlib import Path
 13from types import UnionType
 14from typing import Any, cast
 15
 16from cls.types import GradeTableDict
 17
 18
 19class CommonMethodMixin:
 20    """共通メソッド"""
 21    _section: SectionProxy
 22
 23    def get(self, key: str, fallback: Any = None) -> Any:
 24        """値の取得"""
 25        return self._section.get(key, fallback)
 26
 27    def getint(self, key: str, fallback: int = 0) -> int:
 28        """整数値の取得"""
 29        return self._section.getint(key, fallback)
 30
 31    def getfloat(self, key: str, fallback: float = 0.0) -> float:
 32        """数値の取得"""
 33        return self._section.getfloat(key, fallback)
 34
 35    def getboolean(self, key: str, fallback: bool = False) -> bool:
 36        """真偽値の取得"""
 37        return cast(bool, self._section.getboolean(key, fallback))
 38
 39    def getlist(self, key: str) -> list:
 40        """リストの取得"""
 41        return [x.strip() for x in self._section.get(key, "").split(",")]
 42
 43    def keys(self) -> list:
 44        """キーリストの返却"""
 45        return list(self._section.keys())
 46
 47    def values(self) -> list:
 48        """値リストの返却"""
 49        return list(self._section.values())
 50
 51    def items(self):
 52        """ItemsViewを返却"""
 53        return self._section.items()
 54
 55    def to_dict(self) -> dict[str, str]:
 56        """辞書型に変換"""
 57        return dict(self._section.items())
 58
 59
 60class BaseSection(CommonMethodMixin):
 61    """共通処理"""
 62    def __init__(self, outer, section_name: str):
 63        parser = cast(ConfigParser, outer._parser)
 64        if section_name not in parser:
 65            return
 66        self._section = parser[section_name]
 67
 68        self.initialization()
 69        self.section = section_name  # セクション名保持
 70        logging.info("%s=%s", section_name, self.__dict__)
 71
 72    def initialization(self):
 73        """設定ファイルから値の取り込み"""
 74        self.__dict__.update(self.default_load())
 75        for k in self._section.keys():
 76            if k in self.__dict__:
 77                match type(self.__dict__.get(k)):
 78                    case v_type if v_type is type(str()):
 79                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
 80                    case v_type if v_type is type(int()):
 81                        setattr(self, k, self._section.getint(k, fallback=self.get(k)))
 82                    case v_type if v_type is type(float()):
 83                        setattr(self, k, self._section.getfloat(k, fallback=self.get(k)))
 84                    case v_type if v_type is type(bool()):
 85                        setattr(self, k, self._section.getboolean(k, fallback=self.get(k)))
 86                    case v_type if v_type is type([]):
 87                        v_list = [x.strip() for x in self._section.get(k, fallback=self.get(k)).split(",")]
 88                        setattr(self, k, v_list)
 89                    case v_type if isinstance(v_type, UnionType):
 90                        if set(v_type.__args__) == {str, type(None)}:
 91                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
 92                    case v_type if v_type is type(None):
 93                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
 94                    case _:
 95                        setattr(self, k, self.__dict__.get(k))
 96
 97    def default_load(self) -> dict:
 98        """クラス変数からデフォルト値の取り込み"""
 99        return {k: v for k, v in self.__class__.__dict__.items() if not k.startswith("__") and not callable(v)}
100
101    def to_dict(self) -> dict:
102        """必要なパラメータを辞書型で返す
103
104        Returns:
105            dict: 返却値
106        """
107
108        ret_dict = self.__dict__
109        for key in ["_parser", "_section", "always_argument"]:
110            if key in ret_dict:
111                ret_dict.pop(key)
112
113        return ret_dict
114
115
116class MahjongSection(BaseSection):
117    """mahjongセクション初期値"""
118    rule_version: str = str()
119    """ルール判別識別子"""
120    origin_point: int = 250
121    """配給原点"""
122    return_point: int = 300
123    """返し点"""
124    rank_point: list = [30, 10, -10, -30]
125    """順位点"""
126    ignore_flying: bool = False
127    """トビカウント
128    - True: なし
129    - False: あり
130    """
131    draw_split: bool = False
132    """同点時の順位点
133    - True: 山分けにする
134    - False: 席順で決める
135    """
136    regulations_type2: list = []
137    """メモで役満として扱う単語リスト(カンマ区切り)"""
138
139    def __init__(self, outer, section_name):
140        self._parser = cast(ConfigParser, outer._parser)
141        self.__dict__.update(super().default_load())
142        super().__init__(self, section_name)
143
144        self.rank_point = list(map(int, self.rank_point))  # 数値化
145
146
147class SettingSection(BaseSection):
148    """settingセクション初期値"""
149    slash_command: str = "/mahjong"
150    """スラッシュコマンド名"""
151    thread_report: bool = True
152    """スレッド内にある得点報告を扱う"""
153    time_adjust: int = 12
154    """日付変更後、集計範囲に含める追加時間"""
155    guest_mark: str = "※"
156    """ゲスト無効時に未登録メンバーに付与する印"""
157    reaction_ok: str = "ok"
158    """DBに取り込んだ時に付けるリアクション"""
159    reaction_ng: str = "ng"
160    """DBに取り込んだが正確な値ではない可能性があるときに付けるリアクション"""
161    font_file: str = "ipaexg.ttf"
162    """グラフ描写に使用するフォントファイル"""
163    graph_style: str = "ggplot"
164    """グラフスタイル"""
165    work_dir: str = "work"
166    """生成したファイルを保存するディレクトリ"""
167    ignore_userid: list = []
168    """投稿を無視するユーザのリスト(カンマ区切りで設定)"""
169
170    def __init__(self, outer, section_name: str):
171        self._parser = cast(ConfigParser, outer._parser)
172        super().__init__(self, section_name)
173
174        self.work_dir = os.path.realpath(os.path.join(outer.script_dir, self.work_dir))
175
176        # フォントファイルチェック
177        for chk_dir in (outer.config_dir, outer.script_dir):
178            chk_path = str(os.path.realpath(os.path.join(chk_dir, self.font_file)))
179            if os.path.exists(chk_path):
180                self.font_file = chk_path
181                break
182
183        if chk_path != self.font_file:
184            logging.critical("The specified font file cannot be found.")
185            sys.exit(255)
186
187        logging.notice("fontfile: %s", self.font_file)  # type: ignore
188
189
190class SearchSection(BaseSection):
191    """searchセクション初期値"""
192    keyword: str = "終局"
193    """成績記録キーワード"""
194    channel: str | None = None
195    """テータ突合時に成績記録ワードを検索するチャンネル名"""
196    after: int = 7
197    """データ突合時対象にする日数"""
198    wait: int = 180
199    """指定秒数以内にポストされているデータは突合対象から除外する"""
200
201    def __init__(self, outer, section_name: str):
202        self._parser = cast(ConfigParser, outer._parser)
203        super().__init__(self, section_name)
204
205
206class DatabaseSection(BaseSection):
207    """databaseセクション初期値"""
208    database_file: str = "mahjong.db"
209    """成績管理データベースファイル名"""
210    channel_limitations: list = []
211    """SQLを実行できるチャンネルリスト"""
212    backup_dir: str | None = None
213    """バックアップ先ディレクトリ"""
214
215    def __init__(self, outer, section_name: str):
216        self._parser = cast(ConfigParser, outer._parser)
217        super().__init__(self, section_name)
218
219        self.database_file = os.path.realpath(os.path.join(outer.config_dir, self.database_file))
220        if self.backup_dir:
221            self.backup_dir = os.path.realpath(os.path.join(outer.script_dir, self.backup_dir))
222
223        logging.notice("database: %s", self.database_file)  # type: ignore
224
225
226class MemberSection(BaseSection):
227    "memberセクション初期値"""
228    registration_limit: int = 255
229    """登録メンバー上限数"""
230    character_limit: int = 8
231    """名前に使用できる文字数"""
232    alias_limit: int = 16
233    """別名登録上限数"""
234    guest_name: str = "ゲスト"
235    """未登録メンバー名称"""
236
237    def __init__(self, outer, section_name: str):
238        self._parser = cast(ConfigParser, outer._parser)
239        super().__init__(self, section_name)
240
241
242class TeamSection(BaseSection):
243    """teamセクション初期値"""
244    registration_limit: int = 255
245    """登録チーム上限数"""
246    character_limit: int = 16
247    """チーム名に使用できる文字数"""
248    member_limit: int = 16
249    """チームに所属できるメンバー上限"""
250    friendly_fire: bool = True
251    """チームメイトが同卓しているゲームを集計対象に含めるか"""
252
253    def __init__(self, outer, section_name: str):
254        self._parser = cast(ConfigParser, outer._parser)
255        super().__init__(self, section_name)
256
257
258class AliasSection(BaseSection):
259    """aliasセクション初期値"""
260    results: list = []
261    graph: list = []
262    ranking: list = []
263    report: list = []
264    check: list = []
265    download: list = []
266    member: list = []
267    add: list = []
268    delete: list = []  # "del"はbuilt-inで使用
269    team_create: list = []
270    team_del: list = []
271    team_add: list = []
272    team_remove: list = []
273    team_list: list = []
274    team_clear: list = []
275
276    def __init__(self, outer, section_name: str):
277        self._parser = cast(ConfigParser, outer._parser)
278        super().__init__(self, section_name)
279
280        # デフォルト値として自身と同じ名前のコマンドを登録する #
281        parser = cast(ConfigParser, outer._parser)
282        for k in self.to_dict():
283            x = getattr(self, k)
284            if isinstance(x, list):
285                x.append(k)
286        list_data = [x.strip() for x in str(parser.get("alias", "del", fallback="")).split(",")] + ["del"]
287        self.delete.extend(list_data)
288
289
290class CommentSection(BaseSection):
291    """commentセクション初期値"""
292    group_length: int = 0
293    """コメント検索時の集約文字数(固定指定)"""
294    search_word: str = ""
295    """コメント検索時の検索文字列(固定指定)"""
296
297    def __init__(self, outer, section_name: str):
298        self._parser = cast(ConfigParser, outer._parser)
299        super().__init__(self, section_name)
300
301
302class CommandWord(BaseSection):
303    """チャンネル内呼び出しキーワード初期値"""
304    help: str = "ヘルプ"
305    results: str = "麻雀成績"
306    graph: str = "麻雀グラフ"
307    ranking: str = "麻雀ランキング"
308    report: str = "麻雀成績レポート"
309    member: str = "メンバー一覧"
310    team: str = "チーム一覧"
311    remarks_word: str = "麻雀成績メモ"
312    check: str = "麻雀成績チェック"
313
314    def __init__(self, outer):
315        self._parser = cast(ConfigParser, outer._parser)
316        super().__init__(self, "")
317
318        self.help = self._parser.get("help", "commandword", fallback=CommandWord.help)
319        self.results = self._parser.get("results", "commandword", fallback=CommandWord.results)
320        self.graph = self._parser.get("graph", "commandword", fallback=CommandWord.graph)
321        self.ranking = self._parser.get("ranking", "commandword", fallback=CommandWord.ranking)
322        self.report = self._parser.get("report", "commandword", fallback=CommandWord.report)
323        self.member = self._parser.get("member", "commandword", fallback=CommandWord.member)
324        self.team = self._parser.get("team", "commandword", fallback=CommandWord.team)
325        self.remarks_word = self._parser.get("setting", "remarks_word", fallback=CommandWord.remarks_word)
326        self.check = self._parser.get("database", "commandword", fallback=CommandWord.check)
327
328
329class DropItems(BaseSection):
330    """非表示項目リスト"""
331    results: list = []
332    ranking: list = []
333    report: list = []
334
335    def __init__(self, outer):
336        self._parser = cast(ConfigParser, outer._parser)
337        super().__init__(self, "")
338
339        self.results = [x.strip() for x in self._parser.get("results", "dropitems", fallback="").split(",")]
340        self.ranking = [x.strip() for x in self._parser.get("ranking", "dropitems", fallback="").split(",")]
341        self.report = [x.strip() for x in self._parser.get("report", "dropitems", fallback="").split(",")]
342
343
344class BadgeDisplay(BaseSection):
345    """バッジ表示"""
346    @dataclass
347    class BadgeGradeSpec:
348        """段位"""
349        display: bool = field(default=False)
350        table_name: str = field(default=str())
351        table: GradeTableDict = field(default_factory=lambda: cast(GradeTableDict, dict))
352
353    degree: bool = False
354    status: bool = False
355    grade: "BadgeGradeSpec" = BadgeGradeSpec()
356
357    def __init__(self, outer):
358        self._parser = cast(ConfigParser, outer._parser)
359        super().__init__(self, "")
360
361        self.degree = self._parser.getboolean("degree", "display", fallback=False)
362        self.status = self._parser.getboolean("status", "display", fallback=False)
363        self.grade.display = self._parser.getboolean("grade", "display", fallback=False)
364        self.grade.table_name = self._parser.get("grade", "table_name", fallback="")
365
366
367class SubCommand(BaseSection):
368    """サブコマンド共通クラス"""
369    section: str = ""
370    aggregation_range: str = "当日"
371    """検索範囲未指定時に使用される範囲"""
372    individual: bool = True
373    """個人/チーム集計切替フラグ
374    - True: 個人集計
375    - False: チーム集計
376    """
377    all_player: bool = False
378    daily: bool = True
379    fourfold: bool = True
380    game_results: bool = False
381    guest_skip: bool = True
382    guest_skip2: bool = True
383    ranked: int = 3
384    score_comparisons: bool = False
385    """スコア比較"""
386    statistics: bool = False
387    """統計情報表示"""
388    stipulated: int = 1
389    """規定打数指定"""
390    stipulated_rate: float = 0.05
391    """規定打数計算レート"""
392    unregistered_replace: bool = True
393    """メンバー未登録プレイヤー名をゲストに置き換えるかフラグ
394    - True: 置き換える
395    - False: 置き換えない
396    """
397    anonymous: bool = False
398    """匿名化フラグ"""
399    verbose: bool = False
400    """詳細情報出力フラグ"""
401    versus_matrix: bool = False
402    """対戦マトリックス表示"""
403    collection: str = ""
404    search_word: str = ""
405    group_length: int = 0
406    always_argument: list = []
407    """オプションとして常に付与される文字列(カンマ区切り)"""
408    format: str = ""
409    filename: str = ""
410    interval: int = 80
411
412    def __init__(self, outer, section_name: str):
413        self._parser = cast(ConfigParser, outer._parser)
414        self.section = section_name
415        super().__init__(self, section_name)
416
417    def stipulated_calculation(self, game_count: int) -> int:
418        """規定打数をゲーム数から計算
419
420        Args:
421            game_count (int): 指定ゲーム数
422
423        Returns:
424            int: 規定ゲーム数
425        """
426
427        return int(ceil(game_count * self.stipulated_rate) + 1)
428
429
430class AppConfig:
431    """コンフィグ解析クラス"""
432    def __init__(self, path: str):
433        path = os.path.realpath(path)
434        try:
435            self._parser = ConfigParser()
436            self._parser.read(path, encoding="utf-8")
437            logging.notice("configfile: %s", path)  # type: ignore
438        except Exception as e:
439            raise RuntimeError(e) from e
440
441        # 必須セクションチェック
442        for x in ("mahjong", "setting"):
443            if x not in self._parser.sections():
444                logging.critical("Required section not found. (%s)", x)
445                sys.exit(255)
446
447        # オプションセクションチェック
448        option_sections = [
449            "results",
450            "graph",
451            "ranking",
452            "report",
453            "database",
454            "search",
455            "alias",
456            "member",
457            "team",
458            "comment",
459            "regulations",
460            "help",
461        ]
462        for x in option_sections:
463            if x not in self._parser.sections():
464                self._parser.add_section(x)
465
466        # set base directory
467        self.script_dir = os.path.realpath(str(Path(__file__).resolve().parents[1]))
468        self.config_dir = os.path.dirname(os.path.realpath(str(path)))
469        logging.info("script_dir=%s, config_dir=%s", self.script_dir, self.config_dir)
470
471        # 設定値取り込み
472        self.mahjong = MahjongSection(self, "mahjong")
473        self.setting = SettingSection(self, "setting")
474        self.search = SearchSection(self, "search")
475        self.db = DatabaseSection(self, "database")
476        self.member = MemberSection(self, "member")
477        self.team = TeamSection(self, "team")
478        self.alias = AliasSection(self, "alias")
479        self.comment = CommentSection(self, "comment")
480        self.cw = CommandWord(self)  # チャンネル内呼び出しキーワード
481        self.dropitems = DropItems(self)  # 非表示項目
482        self.badge = BadgeDisplay(self)  # バッジ表示
483
484        # サブコマンドデフォルト
485        self.results = SubCommand(self, "results")
486        self.graph = SubCommand(self, "graph")
487        self.ranking = SubCommand(self, "ranking")
488        self.report = SubCommand(self, "report")
489
490        # 共通設定値
491        self.undefined_word: int = 0
492        self.aggregate_unit: str = ""
493
494    def word_list(self) -> list:
495        """設定されている値、キーワードをリスト化する
496
497        Returns:
498            list: リスト化されたキーワード
499        """
500
501        words: list = []
502
503        words.append([self.setting.slash_command])
504        words.append([self.search.keyword])
505
506        for x in self.cw.to_dict().values():
507            words.append([x])
508
509        for k, v in self.alias.to_dict().items():
510            if isinstance(v, list):
511                words.append([k])
512                words.append(v)
513
514        words = list(set(chain.from_iterable(words)))
515        words = ["del" if x == "delete" else x for x in words]
516        words = [x for x in words if x != ""]
517
518        return words
class CommonMethodMixin:
20class CommonMethodMixin:
21    """共通メソッド"""
22    _section: SectionProxy
23
24    def get(self, key: str, fallback: Any = None) -> Any:
25        """値の取得"""
26        return self._section.get(key, fallback)
27
28    def getint(self, key: str, fallback: int = 0) -> int:
29        """整数値の取得"""
30        return self._section.getint(key, fallback)
31
32    def getfloat(self, key: str, fallback: float = 0.0) -> float:
33        """数値の取得"""
34        return self._section.getfloat(key, fallback)
35
36    def getboolean(self, key: str, fallback: bool = False) -> bool:
37        """真偽値の取得"""
38        return cast(bool, self._section.getboolean(key, fallback))
39
40    def getlist(self, key: str) -> list:
41        """リストの取得"""
42        return [x.strip() for x in self._section.get(key, "").split(",")]
43
44    def keys(self) -> list:
45        """キーリストの返却"""
46        return list(self._section.keys())
47
48    def values(self) -> list:
49        """値リストの返却"""
50        return list(self._section.values())
51
52    def items(self):
53        """ItemsViewを返却"""
54        return self._section.items()
55
56    def to_dict(self) -> dict[str, str]:
57        """辞書型に変換"""
58        return dict(self._section.items())

共通メソッド

def get(self, key: str, fallback: Any = None) -> Any:
24    def get(self, key: str, fallback: Any = None) -> Any:
25        """値の取得"""
26        return self._section.get(key, fallback)

値の取得

def getint(self, key: str, fallback: int = 0) -> int:
28    def getint(self, key: str, fallback: int = 0) -> int:
29        """整数値の取得"""
30        return self._section.getint(key, fallback)

整数値の取得

def getfloat(self, key: str, fallback: float = 0.0) -> float:
32    def getfloat(self, key: str, fallback: float = 0.0) -> float:
33        """数値の取得"""
34        return self._section.getfloat(key, fallback)

数値の取得

def getboolean(self, key: str, fallback: bool = False) -> bool:
36    def getboolean(self, key: str, fallback: bool = False) -> bool:
37        """真偽値の取得"""
38        return cast(bool, self._section.getboolean(key, fallback))

真偽値の取得

def getlist(self, key: str) -> list:
40    def getlist(self, key: str) -> list:
41        """リストの取得"""
42        return [x.strip() for x in self._section.get(key, "").split(",")]

リストの取得

def keys(self) -> list:
44    def keys(self) -> list:
45        """キーリストの返却"""
46        return list(self._section.keys())

キーリストの返却

def values(self) -> list:
48    def values(self) -> list:
49        """値リストの返却"""
50        return list(self._section.values())

値リストの返却

def items(self):
52    def items(self):
53        """ItemsViewを返却"""
54        return self._section.items()

ItemsViewを返却

def to_dict(self) -> dict[str, str]:
56    def to_dict(self) -> dict[str, str]:
57        """辞書型に変換"""
58        return dict(self._section.items())

辞書型に変換

class BaseSection(CommonMethodMixin):
 61class BaseSection(CommonMethodMixin):
 62    """共通処理"""
 63    def __init__(self, outer, section_name: str):
 64        parser = cast(ConfigParser, outer._parser)
 65        if section_name not in parser:
 66            return
 67        self._section = parser[section_name]
 68
 69        self.initialization()
 70        self.section = section_name  # セクション名保持
 71        logging.info("%s=%s", section_name, self.__dict__)
 72
 73    def initialization(self):
 74        """設定ファイルから値の取り込み"""
 75        self.__dict__.update(self.default_load())
 76        for k in self._section.keys():
 77            if k in self.__dict__:
 78                match type(self.__dict__.get(k)):
 79                    case v_type if v_type is type(str()):
 80                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
 81                    case v_type if v_type is type(int()):
 82                        setattr(self, k, self._section.getint(k, fallback=self.get(k)))
 83                    case v_type if v_type is type(float()):
 84                        setattr(self, k, self._section.getfloat(k, fallback=self.get(k)))
 85                    case v_type if v_type is type(bool()):
 86                        setattr(self, k, self._section.getboolean(k, fallback=self.get(k)))
 87                    case v_type if v_type is type([]):
 88                        v_list = [x.strip() for x in self._section.get(k, fallback=self.get(k)).split(",")]
 89                        setattr(self, k, v_list)
 90                    case v_type if isinstance(v_type, UnionType):
 91                        if set(v_type.__args__) == {str, type(None)}:
 92                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
 93                    case v_type if v_type is type(None):
 94                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
 95                    case _:
 96                        setattr(self, k, self.__dict__.get(k))
 97
 98    def default_load(self) -> dict:
 99        """クラス変数からデフォルト値の取り込み"""
100        return {k: v for k, v in self.__class__.__dict__.items() if not k.startswith("__") and not callable(v)}
101
102    def to_dict(self) -> dict:
103        """必要なパラメータを辞書型で返す
104
105        Returns:
106            dict: 返却値
107        """
108
109        ret_dict = self.__dict__
110        for key in ["_parser", "_section", "always_argument"]:
111            if key in ret_dict:
112                ret_dict.pop(key)
113
114        return ret_dict

共通処理

BaseSection(outer, section_name: str)
63    def __init__(self, outer, section_name: str):
64        parser = cast(ConfigParser, outer._parser)
65        if section_name not in parser:
66            return
67        self._section = parser[section_name]
68
69        self.initialization()
70        self.section = section_name  # セクション名保持
71        logging.info("%s=%s", section_name, self.__dict__)
section
def initialization(self):
73    def initialization(self):
74        """設定ファイルから値の取り込み"""
75        self.__dict__.update(self.default_load())
76        for k in self._section.keys():
77            if k in self.__dict__:
78                match type(self.__dict__.get(k)):
79                    case v_type if v_type is type(str()):
80                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
81                    case v_type if v_type is type(int()):
82                        setattr(self, k, self._section.getint(k, fallback=self.get(k)))
83                    case v_type if v_type is type(float()):
84                        setattr(self, k, self._section.getfloat(k, fallback=self.get(k)))
85                    case v_type if v_type is type(bool()):
86                        setattr(self, k, self._section.getboolean(k, fallback=self.get(k)))
87                    case v_type if v_type is type([]):
88                        v_list = [x.strip() for x in self._section.get(k, fallback=self.get(k)).split(",")]
89                        setattr(self, k, v_list)
90                    case v_type if isinstance(v_type, UnionType):
91                        if set(v_type.__args__) == {str, type(None)}:
92                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
93                    case v_type if v_type is type(None):
94                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
95                    case _:
96                        setattr(self, k, self.__dict__.get(k))

設定ファイルから値の取り込み

def default_load(self) -> dict:
 98    def default_load(self) -> dict:
 99        """クラス変数からデフォルト値の取り込み"""
100        return {k: v for k, v in self.__class__.__dict__.items() if not k.startswith("__") and not callable(v)}

クラス変数からデフォルト値の取り込み

def to_dict(self) -> dict:
102    def to_dict(self) -> dict:
103        """必要なパラメータを辞書型で返す
104
105        Returns:
106            dict: 返却値
107        """
108
109        ret_dict = self.__dict__
110        for key in ["_parser", "_section", "always_argument"]:
111            if key in ret_dict:
112                ret_dict.pop(key)
113
114        return ret_dict

必要なパラメータを辞書型で返す

Returns:

dict: 返却値

class MahjongSection(BaseSection):
117class MahjongSection(BaseSection):
118    """mahjongセクション初期値"""
119    rule_version: str = str()
120    """ルール判別識別子"""
121    origin_point: int = 250
122    """配給原点"""
123    return_point: int = 300
124    """返し点"""
125    rank_point: list = [30, 10, -10, -30]
126    """順位点"""
127    ignore_flying: bool = False
128    """トビカウント
129    - True: なし
130    - False: あり
131    """
132    draw_split: bool = False
133    """同点時の順位点
134    - True: 山分けにする
135    - False: 席順で決める
136    """
137    regulations_type2: list = []
138    """メモで役満として扱う単語リスト(カンマ区切り)"""
139
140    def __init__(self, outer, section_name):
141        self._parser = cast(ConfigParser, outer._parser)
142        self.__dict__.update(super().default_load())
143        super().__init__(self, section_name)
144
145        self.rank_point = list(map(int, self.rank_point))  # 数値化

mahjongセクション初期値

MahjongSection(outer, section_name)
140    def __init__(self, outer, section_name):
141        self._parser = cast(ConfigParser, outer._parser)
142        self.__dict__.update(super().default_load())
143        super().__init__(self, section_name)
144
145        self.rank_point = list(map(int, self.rank_point))  # 数値化
rule_version: str = ''

ルール判別識別子

origin_point: int = 250

配給原点

return_point: int = 300

返し点

rank_point: list = [30, 10, -10, -30]

順位点

ignore_flying: bool = False

トビカウント

  • True: なし
  • False: あり
draw_split: bool = False

同点時の順位点

  • True: 山分けにする
  • False: 席順で決める
regulations_type2: list = []

メモで役満として扱う単語リスト(カンマ区切り)

class SettingSection(BaseSection):
148class SettingSection(BaseSection):
149    """settingセクション初期値"""
150    slash_command: str = "/mahjong"
151    """スラッシュコマンド名"""
152    thread_report: bool = True
153    """スレッド内にある得点報告を扱う"""
154    time_adjust: int = 12
155    """日付変更後、集計範囲に含める追加時間"""
156    guest_mark: str = "※"
157    """ゲスト無効時に未登録メンバーに付与する印"""
158    reaction_ok: str = "ok"
159    """DBに取り込んだ時に付けるリアクション"""
160    reaction_ng: str = "ng"
161    """DBに取り込んだが正確な値ではない可能性があるときに付けるリアクション"""
162    font_file: str = "ipaexg.ttf"
163    """グラフ描写に使用するフォントファイル"""
164    graph_style: str = "ggplot"
165    """グラフスタイル"""
166    work_dir: str = "work"
167    """生成したファイルを保存するディレクトリ"""
168    ignore_userid: list = []
169    """投稿を無視するユーザのリスト(カンマ区切りで設定)"""
170
171    def __init__(self, outer, section_name: str):
172        self._parser = cast(ConfigParser, outer._parser)
173        super().__init__(self, section_name)
174
175        self.work_dir = os.path.realpath(os.path.join(outer.script_dir, self.work_dir))
176
177        # フォントファイルチェック
178        for chk_dir in (outer.config_dir, outer.script_dir):
179            chk_path = str(os.path.realpath(os.path.join(chk_dir, self.font_file)))
180            if os.path.exists(chk_path):
181                self.font_file = chk_path
182                break
183
184        if chk_path != self.font_file:
185            logging.critical("The specified font file cannot be found.")
186            sys.exit(255)
187
188        logging.notice("fontfile: %s", self.font_file)  # type: ignore

settingセクション初期値

SettingSection(outer, section_name: str)
171    def __init__(self, outer, section_name: str):
172        self._parser = cast(ConfigParser, outer._parser)
173        super().__init__(self, section_name)
174
175        self.work_dir = os.path.realpath(os.path.join(outer.script_dir, self.work_dir))
176
177        # フォントファイルチェック
178        for chk_dir in (outer.config_dir, outer.script_dir):
179            chk_path = str(os.path.realpath(os.path.join(chk_dir, self.font_file)))
180            if os.path.exists(chk_path):
181                self.font_file = chk_path
182                break
183
184        if chk_path != self.font_file:
185            logging.critical("The specified font file cannot be found.")
186            sys.exit(255)
187
188        logging.notice("fontfile: %s", self.font_file)  # type: ignore
slash_command: str = '/mahjong'

スラッシュコマンド名

thread_report: bool = True

スレッド内にある得点報告を扱う

time_adjust: int = 12

日付変更後、集計範囲に含める追加時間

guest_mark: str = '※'

ゲスト無効時に未登録メンバーに付与する印

reaction_ok: str = 'ok'

DBに取り込んだ時に付けるリアクション

reaction_ng: str = 'ng'

DBに取り込んだが正確な値ではない可能性があるときに付けるリアクション

font_file: str = 'ipaexg.ttf'

グラフ描写に使用するフォントファイル

graph_style: str = 'ggplot'

グラフスタイル

work_dir: str = 'work'

生成したファイルを保存するディレクトリ

ignore_userid: list = []

投稿を無視するユーザのリスト(カンマ区切りで設定)

class SearchSection(BaseSection):
191class SearchSection(BaseSection):
192    """searchセクション初期値"""
193    keyword: str = "終局"
194    """成績記録キーワード"""
195    channel: str | None = None
196    """テータ突合時に成績記録ワードを検索するチャンネル名"""
197    after: int = 7
198    """データ突合時対象にする日数"""
199    wait: int = 180
200    """指定秒数以内にポストされているデータは突合対象から除外する"""
201
202    def __init__(self, outer, section_name: str):
203        self._parser = cast(ConfigParser, outer._parser)
204        super().__init__(self, section_name)

searchセクション初期値

SearchSection(outer, section_name: str)
202    def __init__(self, outer, section_name: str):
203        self._parser = cast(ConfigParser, outer._parser)
204        super().__init__(self, section_name)
keyword: str = '終局'

成績記録キーワード

channel: str | None = None

テータ突合時に成績記録ワードを検索するチャンネル名

after: int = 7

データ突合時対象にする日数

wait: int = 180

指定秒数以内にポストされているデータは突合対象から除外する

class DatabaseSection(BaseSection):
207class DatabaseSection(BaseSection):
208    """databaseセクション初期値"""
209    database_file: str = "mahjong.db"
210    """成績管理データベースファイル名"""
211    channel_limitations: list = []
212    """SQLを実行できるチャンネルリスト"""
213    backup_dir: str | None = None
214    """バックアップ先ディレクトリ"""
215
216    def __init__(self, outer, section_name: str):
217        self._parser = cast(ConfigParser, outer._parser)
218        super().__init__(self, section_name)
219
220        self.database_file = os.path.realpath(os.path.join(outer.config_dir, self.database_file))
221        if self.backup_dir:
222            self.backup_dir = os.path.realpath(os.path.join(outer.script_dir, self.backup_dir))
223
224        logging.notice("database: %s", self.database_file)  # type: ignore

databaseセクション初期値

DatabaseSection(outer, section_name: str)
216    def __init__(self, outer, section_name: str):
217        self._parser = cast(ConfigParser, outer._parser)
218        super().__init__(self, section_name)
219
220        self.database_file = os.path.realpath(os.path.join(outer.config_dir, self.database_file))
221        if self.backup_dir:
222            self.backup_dir = os.path.realpath(os.path.join(outer.script_dir, self.backup_dir))
223
224        logging.notice("database: %s", self.database_file)  # type: ignore
database_file: str = 'mahjong.db'

成績管理データベースファイル名

channel_limitations: list = []

SQLを実行できるチャンネルリスト

backup_dir: str | None = None

バックアップ先ディレクトリ

class MemberSection(BaseSection):
227class MemberSection(BaseSection):
228    "memberセクション初期値"""
229    registration_limit: int = 255
230    """登録メンバー上限数"""
231    character_limit: int = 8
232    """名前に使用できる文字数"""
233    alias_limit: int = 16
234    """別名登録上限数"""
235    guest_name: str = "ゲスト"
236    """未登録メンバー名称"""
237
238    def __init__(self, outer, section_name: str):
239        self._parser = cast(ConfigParser, outer._parser)
240        super().__init__(self, section_name)

memberセクション初期値

MemberSection(outer, section_name: str)
238    def __init__(self, outer, section_name: str):
239        self._parser = cast(ConfigParser, outer._parser)
240        super().__init__(self, section_name)
registration_limit: int = 255

登録メンバー上限数

character_limit: int = 8

名前に使用できる文字数

alias_limit: int = 16

別名登録上限数

guest_name: str = 'ゲスト'

未登録メンバー名称

class TeamSection(BaseSection):
243class TeamSection(BaseSection):
244    """teamセクション初期値"""
245    registration_limit: int = 255
246    """登録チーム上限数"""
247    character_limit: int = 16
248    """チーム名に使用できる文字数"""
249    member_limit: int = 16
250    """チームに所属できるメンバー上限"""
251    friendly_fire: bool = True
252    """チームメイトが同卓しているゲームを集計対象に含めるか"""
253
254    def __init__(self, outer, section_name: str):
255        self._parser = cast(ConfigParser, outer._parser)
256        super().__init__(self, section_name)

teamセクション初期値

TeamSection(outer, section_name: str)
254    def __init__(self, outer, section_name: str):
255        self._parser = cast(ConfigParser, outer._parser)
256        super().__init__(self, section_name)
registration_limit: int = 255

登録チーム上限数

character_limit: int = 16

チーム名に使用できる文字数

member_limit: int = 16

チームに所属できるメンバー上限

friendly_fire: bool = True

チームメイトが同卓しているゲームを集計対象に含めるか

class AliasSection(BaseSection):
259class AliasSection(BaseSection):
260    """aliasセクション初期値"""
261    results: list = []
262    graph: list = []
263    ranking: list = []
264    report: list = []
265    check: list = []
266    download: list = []
267    member: list = []
268    add: list = []
269    delete: list = []  # "del"はbuilt-inで使用
270    team_create: list = []
271    team_del: list = []
272    team_add: list = []
273    team_remove: list = []
274    team_list: list = []
275    team_clear: list = []
276
277    def __init__(self, outer, section_name: str):
278        self._parser = cast(ConfigParser, outer._parser)
279        super().__init__(self, section_name)
280
281        # デフォルト値として自身と同じ名前のコマンドを登録する #
282        parser = cast(ConfigParser, outer._parser)
283        for k in self.to_dict():
284            x = getattr(self, k)
285            if isinstance(x, list):
286                x.append(k)
287        list_data = [x.strip() for x in str(parser.get("alias", "del", fallback="")).split(",")] + ["del"]
288        self.delete.extend(list_data)

aliasセクション初期値

AliasSection(outer, section_name: str)
277    def __init__(self, outer, section_name: str):
278        self._parser = cast(ConfigParser, outer._parser)
279        super().__init__(self, section_name)
280
281        # デフォルト値として自身と同じ名前のコマンドを登録する #
282        parser = cast(ConfigParser, outer._parser)
283        for k in self.to_dict():
284            x = getattr(self, k)
285            if isinstance(x, list):
286                x.append(k)
287        list_data = [x.strip() for x in str(parser.get("alias", "del", fallback="")).split(",")] + ["del"]
288        self.delete.extend(list_data)
results: list = []
graph: list = []
ranking: list = []
report: list = []
check: list = []
download: list = []
member: list = []
add: list = []
delete: list = []
team_create: list = []
team_del: list = []
team_add: list = []
team_remove: list = []
team_list: list = []
team_clear: list = []
class CommentSection(BaseSection):
291class CommentSection(BaseSection):
292    """commentセクション初期値"""
293    group_length: int = 0
294    """コメント検索時の集約文字数(固定指定)"""
295    search_word: str = ""
296    """コメント検索時の検索文字列(固定指定)"""
297
298    def __init__(self, outer, section_name: str):
299        self._parser = cast(ConfigParser, outer._parser)
300        super().__init__(self, section_name)

commentセクション初期値

CommentSection(outer, section_name: str)
298    def __init__(self, outer, section_name: str):
299        self._parser = cast(ConfigParser, outer._parser)
300        super().__init__(self, section_name)
group_length: int = 0

コメント検索時の集約文字数(固定指定)

search_word: str = ''

コメント検索時の検索文字列(固定指定)

class CommandWord(BaseSection):
303class CommandWord(BaseSection):
304    """チャンネル内呼び出しキーワード初期値"""
305    help: str = "ヘルプ"
306    results: str = "麻雀成績"
307    graph: str = "麻雀グラフ"
308    ranking: str = "麻雀ランキング"
309    report: str = "麻雀成績レポート"
310    member: str = "メンバー一覧"
311    team: str = "チーム一覧"
312    remarks_word: str = "麻雀成績メモ"
313    check: str = "麻雀成績チェック"
314
315    def __init__(self, outer):
316        self._parser = cast(ConfigParser, outer._parser)
317        super().__init__(self, "")
318
319        self.help = self._parser.get("help", "commandword", fallback=CommandWord.help)
320        self.results = self._parser.get("results", "commandword", fallback=CommandWord.results)
321        self.graph = self._parser.get("graph", "commandword", fallback=CommandWord.graph)
322        self.ranking = self._parser.get("ranking", "commandword", fallback=CommandWord.ranking)
323        self.report = self._parser.get("report", "commandword", fallback=CommandWord.report)
324        self.member = self._parser.get("member", "commandword", fallback=CommandWord.member)
325        self.team = self._parser.get("team", "commandword", fallback=CommandWord.team)
326        self.remarks_word = self._parser.get("setting", "remarks_word", fallback=CommandWord.remarks_word)
327        self.check = self._parser.get("database", "commandword", fallback=CommandWord.check)

チャンネル内呼び出しキーワード初期値

CommandWord(outer)
315    def __init__(self, outer):
316        self._parser = cast(ConfigParser, outer._parser)
317        super().__init__(self, "")
318
319        self.help = self._parser.get("help", "commandword", fallback=CommandWord.help)
320        self.results = self._parser.get("results", "commandword", fallback=CommandWord.results)
321        self.graph = self._parser.get("graph", "commandword", fallback=CommandWord.graph)
322        self.ranking = self._parser.get("ranking", "commandword", fallback=CommandWord.ranking)
323        self.report = self._parser.get("report", "commandword", fallback=CommandWord.report)
324        self.member = self._parser.get("member", "commandword", fallback=CommandWord.member)
325        self.team = self._parser.get("team", "commandword", fallback=CommandWord.team)
326        self.remarks_word = self._parser.get("setting", "remarks_word", fallback=CommandWord.remarks_word)
327        self.check = self._parser.get("database", "commandword", fallback=CommandWord.check)
help: str = 'ヘルプ'
results: str = '麻雀成績'
graph: str = '麻雀グラフ'
ranking: str = '麻雀ランキング'
report: str = '麻雀成績レポート'
member: str = 'メンバー一覧'
team: str = 'チーム一覧'
remarks_word: str = '麻雀成績メモ'
check: str = '麻雀成績チェック'
class DropItems(BaseSection):
330class DropItems(BaseSection):
331    """非表示項目リスト"""
332    results: list = []
333    ranking: list = []
334    report: list = []
335
336    def __init__(self, outer):
337        self._parser = cast(ConfigParser, outer._parser)
338        super().__init__(self, "")
339
340        self.results = [x.strip() for x in self._parser.get("results", "dropitems", fallback="").split(",")]
341        self.ranking = [x.strip() for x in self._parser.get("ranking", "dropitems", fallback="").split(",")]
342        self.report = [x.strip() for x in self._parser.get("report", "dropitems", fallback="").split(",")]

非表示項目リスト

DropItems(outer)
336    def __init__(self, outer):
337        self._parser = cast(ConfigParser, outer._parser)
338        super().__init__(self, "")
339
340        self.results = [x.strip() for x in self._parser.get("results", "dropitems", fallback="").split(",")]
341        self.ranking = [x.strip() for x in self._parser.get("ranking", "dropitems", fallback="").split(",")]
342        self.report = [x.strip() for x in self._parser.get("report", "dropitems", fallback="").split(",")]
results: list = []
ranking: list = []
report: list = []
class BadgeDisplay(BaseSection):
345class BadgeDisplay(BaseSection):
346    """バッジ表示"""
347    @dataclass
348    class BadgeGradeSpec:
349        """段位"""
350        display: bool = field(default=False)
351        table_name: str = field(default=str())
352        table: GradeTableDict = field(default_factory=lambda: cast(GradeTableDict, dict))
353
354    degree: bool = False
355    status: bool = False
356    grade: "BadgeGradeSpec" = BadgeGradeSpec()
357
358    def __init__(self, outer):
359        self._parser = cast(ConfigParser, outer._parser)
360        super().__init__(self, "")
361
362        self.degree = self._parser.getboolean("degree", "display", fallback=False)
363        self.status = self._parser.getboolean("status", "display", fallback=False)
364        self.grade.display = self._parser.getboolean("grade", "display", fallback=False)
365        self.grade.table_name = self._parser.get("grade", "table_name", fallback="")

バッジ表示

BadgeDisplay(outer)
358    def __init__(self, outer):
359        self._parser = cast(ConfigParser, outer._parser)
360        super().__init__(self, "")
361
362        self.degree = self._parser.getboolean("degree", "display", fallback=False)
363        self.status = self._parser.getboolean("status", "display", fallback=False)
364        self.grade.display = self._parser.getboolean("grade", "display", fallback=False)
365        self.grade.table_name = self._parser.get("grade", "table_name", fallback="")
degree: bool = False
status: bool = False
grade: BadgeDisplay.BadgeGradeSpec = BadgeDisplay.BadgeGradeSpec(display=False, table_name='', table=<class 'dict'>)
@dataclass
class BadgeDisplay.BadgeGradeSpec:
347    @dataclass
348    class BadgeGradeSpec:
349        """段位"""
350        display: bool = field(default=False)
351        table_name: str = field(default=str())
352        table: GradeTableDict = field(default_factory=lambda: cast(GradeTableDict, dict))

段位

BadgeDisplay.BadgeGradeSpec( display: bool = False, table_name: str = '', table: cls.types.GradeTableDict = <factory>)
display: bool = False
table_name: str = ''
class SubCommand(BaseSection):
368class SubCommand(BaseSection):
369    """サブコマンド共通クラス"""
370    section: str = ""
371    aggregation_range: str = "当日"
372    """検索範囲未指定時に使用される範囲"""
373    individual: bool = True
374    """個人/チーム集計切替フラグ
375    - True: 個人集計
376    - False: チーム集計
377    """
378    all_player: bool = False
379    daily: bool = True
380    fourfold: bool = True
381    game_results: bool = False
382    guest_skip: bool = True
383    guest_skip2: bool = True
384    ranked: int = 3
385    score_comparisons: bool = False
386    """スコア比較"""
387    statistics: bool = False
388    """統計情報表示"""
389    stipulated: int = 1
390    """規定打数指定"""
391    stipulated_rate: float = 0.05
392    """規定打数計算レート"""
393    unregistered_replace: bool = True
394    """メンバー未登録プレイヤー名をゲストに置き換えるかフラグ
395    - True: 置き換える
396    - False: 置き換えない
397    """
398    anonymous: bool = False
399    """匿名化フラグ"""
400    verbose: bool = False
401    """詳細情報出力フラグ"""
402    versus_matrix: bool = False
403    """対戦マトリックス表示"""
404    collection: str = ""
405    search_word: str = ""
406    group_length: int = 0
407    always_argument: list = []
408    """オプションとして常に付与される文字列(カンマ区切り)"""
409    format: str = ""
410    filename: str = ""
411    interval: int = 80
412
413    def __init__(self, outer, section_name: str):
414        self._parser = cast(ConfigParser, outer._parser)
415        self.section = section_name
416        super().__init__(self, section_name)
417
418    def stipulated_calculation(self, game_count: int) -> int:
419        """規定打数をゲーム数から計算
420
421        Args:
422            game_count (int): 指定ゲーム数
423
424        Returns:
425            int: 規定ゲーム数
426        """
427
428        return int(ceil(game_count * self.stipulated_rate) + 1)

サブコマンド共通クラス

SubCommand(outer, section_name: str)
413    def __init__(self, outer, section_name: str):
414        self._parser = cast(ConfigParser, outer._parser)
415        self.section = section_name
416        super().__init__(self, section_name)
section: str = ''
aggregation_range: str = '当日'

検索範囲未指定時に使用される範囲

individual: bool = True

個人/チーム集計切替フラグ

  • True: 個人集計
  • False: チーム集計
all_player: bool = False
daily: bool = True
fourfold: bool = True
game_results: bool = False
guest_skip: bool = True
guest_skip2: bool = True
ranked: int = 3
score_comparisons: bool = False

スコア比較

statistics: bool = False

統計情報表示

stipulated: int = 1

規定打数指定

stipulated_rate: float = 0.05

規定打数計算レート

unregistered_replace: bool = True

メンバー未登録プレイヤー名をゲストに置き換えるかフラグ

  • True: 置き換える
  • False: 置き換えない
anonymous: bool = False

匿名化フラグ

verbose: bool = False

詳細情報出力フラグ

versus_matrix: bool = False

対戦マトリックス表示

collection: str = ''
search_word: str = ''
group_length: int = 0
always_argument: list = []

オプションとして常に付与される文字列(カンマ区切り)

format: str = ''
filename: str = ''
interval: int = 80
def stipulated_calculation(self, game_count: int) -> int:
418    def stipulated_calculation(self, game_count: int) -> int:
419        """規定打数をゲーム数から計算
420
421        Args:
422            game_count (int): 指定ゲーム数
423
424        Returns:
425            int: 規定ゲーム数
426        """
427
428        return int(ceil(game_count * self.stipulated_rate) + 1)

規定打数をゲーム数から計算

Arguments:
  • game_count (int): 指定ゲーム数
Returns:

int: 規定ゲーム数

class AppConfig:
431class AppConfig:
432    """コンフィグ解析クラス"""
433    def __init__(self, path: str):
434        path = os.path.realpath(path)
435        try:
436            self._parser = ConfigParser()
437            self._parser.read(path, encoding="utf-8")
438            logging.notice("configfile: %s", path)  # type: ignore
439        except Exception as e:
440            raise RuntimeError(e) from e
441
442        # 必須セクションチェック
443        for x in ("mahjong", "setting"):
444            if x not in self._parser.sections():
445                logging.critical("Required section not found. (%s)", x)
446                sys.exit(255)
447
448        # オプションセクションチェック
449        option_sections = [
450            "results",
451            "graph",
452            "ranking",
453            "report",
454            "database",
455            "search",
456            "alias",
457            "member",
458            "team",
459            "comment",
460            "regulations",
461            "help",
462        ]
463        for x in option_sections:
464            if x not in self._parser.sections():
465                self._parser.add_section(x)
466
467        # set base directory
468        self.script_dir = os.path.realpath(str(Path(__file__).resolve().parents[1]))
469        self.config_dir = os.path.dirname(os.path.realpath(str(path)))
470        logging.info("script_dir=%s, config_dir=%s", self.script_dir, self.config_dir)
471
472        # 設定値取り込み
473        self.mahjong = MahjongSection(self, "mahjong")
474        self.setting = SettingSection(self, "setting")
475        self.search = SearchSection(self, "search")
476        self.db = DatabaseSection(self, "database")
477        self.member = MemberSection(self, "member")
478        self.team = TeamSection(self, "team")
479        self.alias = AliasSection(self, "alias")
480        self.comment = CommentSection(self, "comment")
481        self.cw = CommandWord(self)  # チャンネル内呼び出しキーワード
482        self.dropitems = DropItems(self)  # 非表示項目
483        self.badge = BadgeDisplay(self)  # バッジ表示
484
485        # サブコマンドデフォルト
486        self.results = SubCommand(self, "results")
487        self.graph = SubCommand(self, "graph")
488        self.ranking = SubCommand(self, "ranking")
489        self.report = SubCommand(self, "report")
490
491        # 共通設定値
492        self.undefined_word: int = 0
493        self.aggregate_unit: str = ""
494
495    def word_list(self) -> list:
496        """設定されている値、キーワードをリスト化する
497
498        Returns:
499            list: リスト化されたキーワード
500        """
501
502        words: list = []
503
504        words.append([self.setting.slash_command])
505        words.append([self.search.keyword])
506
507        for x in self.cw.to_dict().values():
508            words.append([x])
509
510        for k, v in self.alias.to_dict().items():
511            if isinstance(v, list):
512                words.append([k])
513                words.append(v)
514
515        words = list(set(chain.from_iterable(words)))
516        words = ["del" if x == "delete" else x for x in words]
517        words = [x for x in words if x != ""]
518
519        return words

コンフィグ解析クラス

AppConfig(path: str)
433    def __init__(self, path: str):
434        path = os.path.realpath(path)
435        try:
436            self._parser = ConfigParser()
437            self._parser.read(path, encoding="utf-8")
438            logging.notice("configfile: %s", path)  # type: ignore
439        except Exception as e:
440            raise RuntimeError(e) from e
441
442        # 必須セクションチェック
443        for x in ("mahjong", "setting"):
444            if x not in self._parser.sections():
445                logging.critical("Required section not found. (%s)", x)
446                sys.exit(255)
447
448        # オプションセクションチェック
449        option_sections = [
450            "results",
451            "graph",
452            "ranking",
453            "report",
454            "database",
455            "search",
456            "alias",
457            "member",
458            "team",
459            "comment",
460            "regulations",
461            "help",
462        ]
463        for x in option_sections:
464            if x not in self._parser.sections():
465                self._parser.add_section(x)
466
467        # set base directory
468        self.script_dir = os.path.realpath(str(Path(__file__).resolve().parents[1]))
469        self.config_dir = os.path.dirname(os.path.realpath(str(path)))
470        logging.info("script_dir=%s, config_dir=%s", self.script_dir, self.config_dir)
471
472        # 設定値取り込み
473        self.mahjong = MahjongSection(self, "mahjong")
474        self.setting = SettingSection(self, "setting")
475        self.search = SearchSection(self, "search")
476        self.db = DatabaseSection(self, "database")
477        self.member = MemberSection(self, "member")
478        self.team = TeamSection(self, "team")
479        self.alias = AliasSection(self, "alias")
480        self.comment = CommentSection(self, "comment")
481        self.cw = CommandWord(self)  # チャンネル内呼び出しキーワード
482        self.dropitems = DropItems(self)  # 非表示項目
483        self.badge = BadgeDisplay(self)  # バッジ表示
484
485        # サブコマンドデフォルト
486        self.results = SubCommand(self, "results")
487        self.graph = SubCommand(self, "graph")
488        self.ranking = SubCommand(self, "ranking")
489        self.report = SubCommand(self, "report")
490
491        # 共通設定値
492        self.undefined_word: int = 0
493        self.aggregate_unit: str = ""
script_dir
config_dir
mahjong
setting
search
db
member
team
alias
comment
cw
dropitems
badge
results
graph
ranking
report
undefined_word: int
aggregate_unit: str
def word_list(self) -> list:
495    def word_list(self) -> list:
496        """設定されている値、キーワードをリスト化する
497
498        Returns:
499            list: リスト化されたキーワード
500        """
501
502        words: list = []
503
504        words.append([self.setting.slash_command])
505        words.append([self.search.keyword])
506
507        for x in self.cw.to_dict().values():
508            words.append([x])
509
510        for k, v in self.alias.to_dict().items():
511            if isinstance(v, list):
512                words.append([k])
513                words.append(v)
514
515        words = list(set(chain.from_iterable(words)))
516        words = ["del" if x == "delete" else x for x in words]
517        words = [x for x in words if x != ""]
518
519        return words

設定されている値、キーワードをリスト化する

Returns:

list: リスト化されたキーワード