cls.config

cls/config.py

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

共通メソッド

def get(self, key: str, fallback: Any = None) -> Any:
40    def get(self, key: str, fallback: Any = None) -> Any:
41        """値の取得"""
42        return self._section.get(key, fallback)

値の取得

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

整数値の取得

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

数値の取得

def getboolean(self, key: str, fallback: bool = False) -> bool:
52    def getboolean(self, key: str, fallback: bool = False) -> bool:
53        """真偽値の取得"""
54        return self._section.getboolean(key, fallback)

真偽値の取得

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

リストの取得

def keys(self) -> list:
60    def keys(self) -> list:
61        """キーリストの返却"""
62        return list(self._section.keys())

キーリストの返却

def values(self) -> list:
64    def values(self) -> list:
65        """値リストの返却"""
66        return list(self._section.values())

値リストの返却

def items(self):
68    def items(self):
69        """ItemsViewを返却"""
70        return self._section.items()

ItemsViewを返却

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

辞書型に変換

class BaseSection(CommonMethodMixin):
 77class BaseSection(CommonMethodMixin):
 78    """共通処理"""
 79
 80    def __init__(self, outer: SubClassType, section_name: str):
 81        parser = outer._parser
 82        if section_name not in parser:
 83            return
 84        self._section = parser[section_name]
 85
 86        self.initialization()
 87        self.section = section_name  # セクション名保持
 88
 89    def __repr__(self) -> str:
 90        return str({k: v for k, v in vars(self).items() if not str(k).startswith("_")})
 91
 92    def initialization(self):
 93        """設定ファイルから値の取り込み"""
 94        for k in self._section.keys():
 95            if k in self.__dict__:
 96                match type(self.__dict__.get(k)):
 97                    case v_type if v_type is str:
 98                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
 99                    case v_type if v_type is int:
100                        setattr(self, k, self._section.getint(k, fallback=self.get(k)))
101                    case v_type if v_type is float:
102                        setattr(self, k, self._section.getfloat(k, fallback=self.get(k)))
103                    case v_type if v_type is bool:
104                        setattr(self, k, self._section.getboolean(k, fallback=self.get(k)))
105                    case v_type if v_type is list:
106                        v_list = [x.strip() for x in self._section.get(k, fallback=self.get(k)).split(",")]
107                        current_list = getattr(self, k)
108                        if isinstance(current_list, list) and current_list:  # 設定済みリストは追加
109                            current_list.extend(v_list)
110                        else:
111                            setattr(self, k, v_list)
112                    case v_type if v_type is UnionType:  # 文字列 or None
113                        if set(v_type.__args__) == {str, type(None)}:
114                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
115                    case v_type if v_type is PosixPath:
116                        setattr(self, k, Path(self._section.get(k, fallback=self.get(k))))
117                    case v_type if v_type is NoneType:
118                        if k in ["backup_dir"]:  # ディレクトリを指定する設定はPathで格納
119                            setattr(self, k, Path(self._section.get(k, fallback=self.get(k))))
120                        else:
121                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
122                    case _:
123                        setattr(self, k, self.__dict__.get(k))
124
125    def to_dict(self) -> dict:
126        """必要なパラメータを辞書型で返す
127
128        Returns:
129            dict: 返却値
130        """
131
132        ret_dict: dict = {}
133        for key in vars(self):
134            if key.startswith("_"):
135                continue
136            ret_dict[key] = getattr(self, key)
137
138        return ret_dict

共通処理

BaseSection( outer: Union[MahjongSection, SettingSection, MemberSection, TeamSection, AliasSection, CommentSection, DropItems, BadgeDisplay, SubCommand], section_name: str)
80    def __init__(self, outer: SubClassType, section_name: str):
81        parser = outer._parser
82        if section_name not in parser:
83            return
84        self._section = parser[section_name]
85
86        self.initialization()
87        self.section = section_name  # セクション名保持
section
def initialization(self):
 92    def initialization(self):
 93        """設定ファイルから値の取り込み"""
 94        for k in self._section.keys():
 95            if k in self.__dict__:
 96                match type(self.__dict__.get(k)):
 97                    case v_type if v_type is str:
 98                        setattr(self, k, self._section.get(k, fallback=self.get(k)))
 99                    case v_type if v_type is int:
100                        setattr(self, k, self._section.getint(k, fallback=self.get(k)))
101                    case v_type if v_type is float:
102                        setattr(self, k, self._section.getfloat(k, fallback=self.get(k)))
103                    case v_type if v_type is bool:
104                        setattr(self, k, self._section.getboolean(k, fallback=self.get(k)))
105                    case v_type if v_type is list:
106                        v_list = [x.strip() for x in self._section.get(k, fallback=self.get(k)).split(",")]
107                        current_list = getattr(self, k)
108                        if isinstance(current_list, list) and current_list:  # 設定済みリストは追加
109                            current_list.extend(v_list)
110                        else:
111                            setattr(self, k, v_list)
112                    case v_type if v_type is UnionType:  # 文字列 or None
113                        if set(v_type.__args__) == {str, type(None)}:
114                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
115                    case v_type if v_type is PosixPath:
116                        setattr(self, k, Path(self._section.get(k, fallback=self.get(k))))
117                    case v_type if v_type is NoneType:
118                        if k in ["backup_dir"]:  # ディレクトリを指定する設定はPathで格納
119                            setattr(self, k, Path(self._section.get(k, fallback=self.get(k))))
120                        else:
121                            setattr(self, k, self._section.get(k, fallback=self.get(k)))
122                    case _:
123                        setattr(self, k, self.__dict__.get(k))

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

def to_dict(self) -> dict:
125    def to_dict(self) -> dict:
126        """必要なパラメータを辞書型で返す
127
128        Returns:
129            dict: 返却値
130        """
131
132        ret_dict: dict = {}
133        for key in vars(self):
134            if key.startswith("_"):
135                continue
136            ret_dict[key] = getattr(self, key)
137
138        return ret_dict

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

Returns:

dict: 返却値

class MahjongSection(BaseSection):
141class MahjongSection(BaseSection):
142    """mahjongセクション初期値"""
143
144    def __init__(self, outer: "AppConfig", section_name):
145        self._parser = outer._parser
146
147        # 初期値セット
148        self.rule_version: str = ""
149        """ルール判別識別子"""
150        self.origin_point: int = 250
151        """配給原点"""
152        self.return_point: int = 300
153        """返し点"""
154        self.rank_point: list = []
155        """順位点"""
156        self.ignore_flying: bool = False
157        """トビカウント
158        - True: なし
159        - False: あり
160        """
161        self.draw_split: bool = False
162        """同点時の順位点
163        - True: 山分けにする
164        - False: 席順で決める
165        """
166        self.regulations_type2: list = []
167        """メモで役満として扱う単語リスト(カンマ区切り)"""
168
169        # 設定値取り込み
170        super().__init__(self, section_name)
171
172        # 順位点更新
173        if not self.rank_point:
174            self.rank_point = [30, 10, -10, -30]
175
176        self.rank_point = list(map(int, self.rank_point))  # 数値化

mahjongセクション初期値

MahjongSection(outer: AppConfig, section_name)
144    def __init__(self, outer: "AppConfig", section_name):
145        self._parser = outer._parser
146
147        # 初期値セット
148        self.rule_version: str = ""
149        """ルール判別識別子"""
150        self.origin_point: int = 250
151        """配給原点"""
152        self.return_point: int = 300
153        """返し点"""
154        self.rank_point: list = []
155        """順位点"""
156        self.ignore_flying: bool = False
157        """トビカウント
158        - True: なし
159        - False: あり
160        """
161        self.draw_split: bool = False
162        """同点時の順位点
163        - True: 山分けにする
164        - False: 席順で決める
165        """
166        self.regulations_type2: list = []
167        """メモで役満として扱う単語リスト(カンマ区切り)"""
168
169        # 設定値取り込み
170        super().__init__(self, section_name)
171
172        # 順位点更新
173        if not self.rank_point:
174            self.rank_point = [30, 10, -10, -30]
175
176        self.rank_point = list(map(int, self.rank_point))  # 数値化
rule_version: str

ルール判別識別子

origin_point: int

配給原点

return_point: int

返し点

rank_point: list

順位点

ignore_flying: bool

トビカウント

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

同点時の順位点

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

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

class SettingSection(BaseSection):
179class SettingSection(BaseSection):
180    """settingセクション初期値"""
181
182    def __init__(self, outer: "AppConfig", section_name: str):
183        self._parser = outer._parser
184
185        # 初期値セット
186        self.help: str = "麻雀成績ヘルプ"
187        """ヘルプ表示キーワード"""
188        self.keyword: str = "終局"
189        """成績記録キーワード"""
190        self.remarks_word: str = "麻雀成績メモ"
191        """メモ記録用キーワード"""
192        self.time_adjust: int = 12
193        """日付変更後、集計範囲に含める追加時間"""
194        self.guest_mark: str = "※"
195        """ゲスト無効時に未登録メンバーに付与する印"""
196        self.database_file: Union[Path, str] = Path("mahjong.db")
197        """成績管理データベースファイル名"""
198        self.backup_dir: Optional[Path] = None
199        """バックアップ先ディレクトリ"""
200        self.font_file: Path = Path("ipaexg.ttf")
201        """グラフ描写に使用するフォントファイル"""
202        self.graph_style: str = "ggplot"
203        """グラフスタイル"""
204        self.work_dir: Path = Path("work")
205
206        # 設定値取り込み
207        super().__init__(self, section_name)
208
209        # 作業用ディレクトリ作成
210        if self.work_dir.is_dir():
211            shutil.rmtree(self.work_dir)
212        try:
213            self.work_dir.mkdir(exist_ok=True)
214        except FileExistsError as err:
215            sys.exit(str(err))
216
217        # フォントファイルチェック
218        for chk_dir in (outer.config_dir, outer.script_dir):
219            chk_file = chk_dir / str(self.font_file)
220            if chk_file.exists():
221                self.font_file = chk_file
222                break
223        else:
224            if not self.font_file.exists():
225                logging.critical("The specified font file cannot be found.")
226                sys.exit(255)
227
228        # データベース関連
229        for chk_dir in (outer.config_dir, outer.script_dir):
230            chk_file = chk_dir / str(self.database_file)
231            if chk_file.exists():
232                self.database_file = chk_file
233                break
234
235        if isinstance(self.backup_dir, PosixPath):
236            try:
237                self.backup_dir.mkdir(exist_ok=True)
238            except FileExistsError as err:
239                sys.exit(str(err))

settingセクション初期値

SettingSection(outer: AppConfig, section_name: str)
182    def __init__(self, outer: "AppConfig", section_name: str):
183        self._parser = outer._parser
184
185        # 初期値セット
186        self.help: str = "麻雀成績ヘルプ"
187        """ヘルプ表示キーワード"""
188        self.keyword: str = "終局"
189        """成績記録キーワード"""
190        self.remarks_word: str = "麻雀成績メモ"
191        """メモ記録用キーワード"""
192        self.time_adjust: int = 12
193        """日付変更後、集計範囲に含める追加時間"""
194        self.guest_mark: str = "※"
195        """ゲスト無効時に未登録メンバーに付与する印"""
196        self.database_file: Union[Path, str] = Path("mahjong.db")
197        """成績管理データベースファイル名"""
198        self.backup_dir: Optional[Path] = None
199        """バックアップ先ディレクトリ"""
200        self.font_file: Path = Path("ipaexg.ttf")
201        """グラフ描写に使用するフォントファイル"""
202        self.graph_style: str = "ggplot"
203        """グラフスタイル"""
204        self.work_dir: Path = Path("work")
205
206        # 設定値取り込み
207        super().__init__(self, section_name)
208
209        # 作業用ディレクトリ作成
210        if self.work_dir.is_dir():
211            shutil.rmtree(self.work_dir)
212        try:
213            self.work_dir.mkdir(exist_ok=True)
214        except FileExistsError as err:
215            sys.exit(str(err))
216
217        # フォントファイルチェック
218        for chk_dir in (outer.config_dir, outer.script_dir):
219            chk_file = chk_dir / str(self.font_file)
220            if chk_file.exists():
221                self.font_file = chk_file
222                break
223        else:
224            if not self.font_file.exists():
225                logging.critical("The specified font file cannot be found.")
226                sys.exit(255)
227
228        # データベース関連
229        for chk_dir in (outer.config_dir, outer.script_dir):
230            chk_file = chk_dir / str(self.database_file)
231            if chk_file.exists():
232                self.database_file = chk_file
233                break
234
235        if isinstance(self.backup_dir, PosixPath):
236            try:
237                self.backup_dir.mkdir(exist_ok=True)
238            except FileExistsError as err:
239                sys.exit(str(err))
help: str

ヘルプ表示キーワード

keyword: str

成績記録キーワード

remarks_word: str

メモ記録用キーワード

time_adjust: int

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

guest_mark: str

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

database_file: Union[pathlib.Path, str]

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

backup_dir: Optional[pathlib.Path]

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

font_file: pathlib.Path

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

graph_style: str

グラフスタイル

work_dir: pathlib.Path
class MemberSection(BaseSection):
242class MemberSection(BaseSection):
243    "memberセクション初期値"""
244
245    def __init__(self, outer: "AppConfig", section_name: str):
246        self._parser = outer._parser
247
248        # 初期値セット
249        self.registration_limit: int = 255
250        """登録メンバー上限数"""
251        self.character_limit: int = 8
252        """名前に使用できる文字数"""
253        self.alias_limit: int = 16
254        """別名登録上限数"""
255        self.guest_name: str = "ゲスト"
256        """未登録メンバー名称"""
257
258        # 設定値取り込み
259        super().__init__(self, section_name)
260
261        # 呼び出しキーワード取り込み
262        self.commandword = [x.strip() for x in self._parser.get("member", "commandword", fallback="メンバー一覧").split(",")]

memberセクション初期値

MemberSection(outer: AppConfig, section_name: str)
245    def __init__(self, outer: "AppConfig", section_name: str):
246        self._parser = outer._parser
247
248        # 初期値セット
249        self.registration_limit: int = 255
250        """登録メンバー上限数"""
251        self.character_limit: int = 8
252        """名前に使用できる文字数"""
253        self.alias_limit: int = 16
254        """別名登録上限数"""
255        self.guest_name: str = "ゲスト"
256        """未登録メンバー名称"""
257
258        # 設定値取り込み
259        super().__init__(self, section_name)
260
261        # 呼び出しキーワード取り込み
262        self.commandword = [x.strip() for x in self._parser.get("member", "commandword", fallback="メンバー一覧").split(",")]
registration_limit: int

登録メンバー上限数

character_limit: int

名前に使用できる文字数

alias_limit: int

別名登録上限数

guest_name: str

未登録メンバー名称

commandword
class TeamSection(BaseSection):
265class TeamSection(BaseSection):
266    """teamセクション初期値"""
267
268    def __init__(self, outer: "AppConfig", section_name: str):
269        self._parser = outer._parser
270
271        # 初期値セット
272        self.registration_limit: int = 255
273        """登録チーム上限数"""
274        self.character_limit: int = 16
275        """チーム名に使用できる文字数"""
276        self.member_limit: int = 16
277        """チームに所属できるメンバー上限"""
278        self.friendly_fire: bool = True
279        """チームメイトが同卓しているゲームを集計対象に含めるか"""
280
281        # 設定値取り込み
282        super().__init__(self, section_name)
283
284        # 呼び出しキーワード取り込み
285        self.commandword = [x.strip() for x in self._parser.get("team", "commandword", fallback="チーム一覧").split(",")]

teamセクション初期値

TeamSection(outer: AppConfig, section_name: str)
268    def __init__(self, outer: "AppConfig", section_name: str):
269        self._parser = outer._parser
270
271        # 初期値セット
272        self.registration_limit: int = 255
273        """登録チーム上限数"""
274        self.character_limit: int = 16
275        """チーム名に使用できる文字数"""
276        self.member_limit: int = 16
277        """チームに所属できるメンバー上限"""
278        self.friendly_fire: bool = True
279        """チームメイトが同卓しているゲームを集計対象に含めるか"""
280
281        # 設定値取り込み
282        super().__init__(self, section_name)
283
284        # 呼び出しキーワード取り込み
285        self.commandword = [x.strip() for x in self._parser.get("team", "commandword", fallback="チーム一覧").split(",")]
registration_limit: int

登録チーム上限数

character_limit: int

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

member_limit: int

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

friendly_fire: bool

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

commandword
class AliasSection(BaseSection):
288class AliasSection(BaseSection):
289    """aliasセクション初期値"""
290
291    def __init__(self, outer: "AppConfig", section_name: str):
292        self._parser = outer._parser
293
294        # 初期値セット
295        self.results: list = ["成績"]
296        self.graph: list = ["グラフ"]
297        self.ranking: list = ["ランキング"]
298        self.report: list = ["レポート"]
299        self.download: list = ["ダウンロード"]
300        self.member: list = ["userlist", "member_list"]
301        self.add: list = []
302        self.delete: list = ["del"]  # "del"はbuilt-inで使用
303        self.team_create: list = []
304        self.team_del: list = []
305        self.team_add: list = []
306        self.team_remove: list = []
307        self.team_list: list = []
308        self.team_clear: list = []
309
310        # 設定値取り込み
311        super().__init__(self, section_name)
312
313        # デフォルト値として自身と同じ名前のコマンドを登録する #
314        for k in self.to_dict():
315            current_list = getattr(self, k)
316            if isinstance(current_list, list):
317                current_list.append(k)
318        # delのエイリアス取り込み(設定ファイルに`delete`と書かれていない)
319        list_data = [x.strip() for x in str(self._parser.get("alias", "del", fallback="")).split(",")]
320        self.delete.extend(list_data)

aliasセクション初期値

AliasSection(outer: AppConfig, section_name: str)
291    def __init__(self, outer: "AppConfig", section_name: str):
292        self._parser = outer._parser
293
294        # 初期値セット
295        self.results: list = ["成績"]
296        self.graph: list = ["グラフ"]
297        self.ranking: list = ["ランキング"]
298        self.report: list = ["レポート"]
299        self.download: list = ["ダウンロード"]
300        self.member: list = ["userlist", "member_list"]
301        self.add: list = []
302        self.delete: list = ["del"]  # "del"はbuilt-inで使用
303        self.team_create: list = []
304        self.team_del: list = []
305        self.team_add: list = []
306        self.team_remove: list = []
307        self.team_list: list = []
308        self.team_clear: list = []
309
310        # 設定値取り込み
311        super().__init__(self, section_name)
312
313        # デフォルト値として自身と同じ名前のコマンドを登録する #
314        for k in self.to_dict():
315            current_list = getattr(self, k)
316            if isinstance(current_list, list):
317                current_list.append(k)
318        # delのエイリアス取り込み(設定ファイルに`delete`と書かれていない)
319        list_data = [x.strip() for x in str(self._parser.get("alias", "del", fallback="")).split(",")]
320        self.delete.extend(list_data)
results: list
graph: list
ranking: list
report: 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):
323class CommentSection(BaseSection):
324    """commentセクション初期値"""
325
326    def __init__(self, outer: "AppConfig", section_name: str):
327        self._parser = outer._parser
328
329        # 初期値セット
330        self.group_length: int = 0
331        """コメント検索時の集約文字数(固定指定)"""
332        self.search_word: str = ""
333        """コメント検索時の検索文字列(固定指定)"""
334
335        # 設定値取り込み
336        super().__init__(self, section_name)

commentセクション初期値

CommentSection(outer: AppConfig, section_name: str)
326    def __init__(self, outer: "AppConfig", section_name: str):
327        self._parser = outer._parser
328
329        # 初期値セット
330        self.group_length: int = 0
331        """コメント検索時の集約文字数(固定指定)"""
332        self.search_word: str = ""
333        """コメント検索時の検索文字列(固定指定)"""
334
335        # 設定値取り込み
336        super().__init__(self, section_name)
group_length: int

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

search_word: str

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

class DropItems(BaseSection):
339class DropItems(BaseSection):
340    """非表示項目リスト"""
341
342    def __init__(self, outer: "AppConfig"):
343        self._parser = outer._parser
344
345        # 初期値セット
346        self.results: list = []
347        self.ranking: list = []
348        self.report: list = []
349
350        # 設定値取り込み
351        super().__init__(self, "")
352
353        self.results = [x.strip() for x in self._parser.get("results", "dropitems", fallback="").split(",")]
354        self.ranking = [x.strip() for x in self._parser.get("ranking", "dropitems", fallback="").split(",")]
355        self.report = [x.strip() for x in self._parser.get("report", "dropitems", fallback="").split(",")]

非表示項目リスト

DropItems(outer: AppConfig)
342    def __init__(self, outer: "AppConfig"):
343        self._parser = outer._parser
344
345        # 初期値セット
346        self.results: list = []
347        self.ranking: list = []
348        self.report: list = []
349
350        # 設定値取り込み
351        super().__init__(self, "")
352
353        self.results = [x.strip() for x in self._parser.get("results", "dropitems", fallback="").split(",")]
354        self.ranking = [x.strip() for x in self._parser.get("ranking", "dropitems", fallback="").split(",")]
355        self.report = [x.strip() for x in self._parser.get("report", "dropitems", fallback="").split(",")]
results: list
ranking: list
report: list
class BadgeDisplay(BaseSection):
358class BadgeDisplay(BaseSection):
359    """バッジ表示"""
360
361    @dataclass
362    class BadgeGradeSpec:
363        """段位"""
364
365        table_name: str = field(default=str())
366        table: GradeTableDict = field(default_factory=GradeTableDict)
367
368    grade: "BadgeGradeSpec" = BadgeGradeSpec()
369
370    def __init__(self, outer: "AppConfig"):
371        self._parser = outer._parser
372        super().__init__(self, "")
373
374        self.grade.table_name = self._parser.get("grade", "table_name", fallback="")

バッジ表示

BadgeDisplay(outer: AppConfig)
370    def __init__(self, outer: "AppConfig"):
371        self._parser = outer._parser
372        super().__init__(self, "")
373
374        self.grade.table_name = self._parser.get("grade", "table_name", fallback="")
grade: BadgeDisplay.BadgeGradeSpec = BadgeDisplay.BadgeGradeSpec(table_name='', table={})
@dataclass
class BadgeDisplay.BadgeGradeSpec:
361    @dataclass
362    class BadgeGradeSpec:
363        """段位"""
364
365        table_name: str = field(default=str())
366        table: GradeTableDict = field(default_factory=GradeTableDict)

段位

BadgeDisplay.BadgeGradeSpec(table_name: str = '', table: libs.types.GradeTableDict = <factory>)
table_name: str = ''
class SubCommand(BaseSection):
377class SubCommand(BaseSection):
378    """サブコマンド共通クラス"""
379
380    def __init__(self, outer: "AppConfig", section_name: str, default: str):
381        self._parser = outer._parser
382        self.section = section_name
383
384        # 初期値セット
385        self.section: str = ""
386        self.commandword: list = []
387        """呼び出しキーワード"""
388        self.aggregation_range: str = "当日"
389        """検索範囲未指定時に使用される範囲"""
390        self.individual: bool = True
391        """個人/チーム集計切替フラグ
392        - True: 個人集計
393        - False: チーム集計
394        """
395        self.all_player: bool = False
396        self.daily: bool = True
397        self.fourfold: bool = True
398        self.game_results: bool = False
399        self.guest_skip: bool = True
400        self.guest_skip2: bool = True
401        self.ranked: int = 3
402        self.score_comparisons: bool = False
403        """スコア比較"""
404        self.statistics: bool = False
405        """統計情報表示"""
406        self.stipulated: int = 0
407        """規定打数指定"""
408        self.stipulated_rate: float = 0.05
409        """規定打数計算レート"""
410        self.unregistered_replace: bool = True
411        """メンバー未登録プレイヤー名をゲストに置き換えるかフラグ
412        - True: 置き換える
413        - False: 置き換えない
414        """
415        self.anonymous: bool = False
416        """匿名化フラグ"""
417        self.verbose: bool = False
418        """詳細情報出力フラグ"""
419        self.versus_matrix: bool = False
420        """対戦マトリックス表示"""
421        self.collection: str = ""
422        self.search_word: str = ""
423        self.group_length: int = 0
424        self.always_argument: list = []
425        """オプションとして常に付与される文字列"""
426        self.format: str = ""
427        self.filename: str = ""
428        self.interval: int = 80
429
430        # 設定値取り込み
431        super().__init__(self, section_name)
432
433        # 呼び出しキーワード取り込み
434        self.commandword = [x.strip() for x in self._parser.get(section_name, "commandword", fallback=default).split(",")]
435
436    def stipulated_calculation(self, game_count: int) -> int:
437        """規定打数をゲーム数から計算
438
439        Args:
440            game_count (int): 指定ゲーム数
441
442        Returns:
443            int: 規定ゲーム数
444        """
445
446        return int(ceil(game_count * self.stipulated_rate) + 1)

サブコマンド共通クラス

SubCommand(outer: AppConfig, section_name: str, default: str)
380    def __init__(self, outer: "AppConfig", section_name: str, default: str):
381        self._parser = outer._parser
382        self.section = section_name
383
384        # 初期値セット
385        self.section: str = ""
386        self.commandword: list = []
387        """呼び出しキーワード"""
388        self.aggregation_range: str = "当日"
389        """検索範囲未指定時に使用される範囲"""
390        self.individual: bool = True
391        """個人/チーム集計切替フラグ
392        - True: 個人集計
393        - False: チーム集計
394        """
395        self.all_player: bool = False
396        self.daily: bool = True
397        self.fourfold: bool = True
398        self.game_results: bool = False
399        self.guest_skip: bool = True
400        self.guest_skip2: bool = True
401        self.ranked: int = 3
402        self.score_comparisons: bool = False
403        """スコア比較"""
404        self.statistics: bool = False
405        """統計情報表示"""
406        self.stipulated: int = 0
407        """規定打数指定"""
408        self.stipulated_rate: float = 0.05
409        """規定打数計算レート"""
410        self.unregistered_replace: bool = True
411        """メンバー未登録プレイヤー名をゲストに置き換えるかフラグ
412        - True: 置き換える
413        - False: 置き換えない
414        """
415        self.anonymous: bool = False
416        """匿名化フラグ"""
417        self.verbose: bool = False
418        """詳細情報出力フラグ"""
419        self.versus_matrix: bool = False
420        """対戦マトリックス表示"""
421        self.collection: str = ""
422        self.search_word: str = ""
423        self.group_length: int = 0
424        self.always_argument: list = []
425        """オプションとして常に付与される文字列"""
426        self.format: str = ""
427        self.filename: str = ""
428        self.interval: int = 80
429
430        # 設定値取り込み
431        super().__init__(self, section_name)
432
433        # 呼び出しキーワード取り込み
434        self.commandword = [x.strip() for x in self._parser.get(section_name, "commandword", fallback=default).split(",")]
section: str
commandword: list

呼び出しキーワード

aggregation_range: str

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

individual: bool

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

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

スコア比較

statistics: bool

統計情報表示

stipulated: int

規定打数指定

stipulated_rate: float

規定打数計算レート

unregistered_replace: bool

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

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

匿名化フラグ

verbose: bool

詳細情報出力フラグ

versus_matrix: bool

対戦マトリックス表示

collection: str
search_word: str
group_length: int
always_argument: list

オプションとして常に付与される文字列

format: str
filename: str
interval: int
def stipulated_calculation(self, game_count: int) -> int:
436    def stipulated_calculation(self, game_count: int) -> int:
437        """規定打数をゲーム数から計算
438
439        Args:
440            game_count (int): 指定ゲーム数
441
442        Returns:
443            int: 規定ゲーム数
444        """
445
446        return int(ceil(game_count * self.stipulated_rate) + 1)

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

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

int: 規定ゲーム数

class AppConfig:
449class AppConfig:
450    """コンフィグ解析クラス"""
451
452    def __init__(self, config_file: str):
453        _config = Path(config_file)
454        try:
455            self._parser = ConfigParser()
456            self._parser.read(_config, encoding="utf-8")
457        except Exception as err:
458            raise RuntimeError(err) from err
459
460        # 必須セクションチェック
461        for x in ("mahjong", "setting"):
462            if x not in self._parser.sections():
463                logging.critical("Required section not found. (%s)", x)
464                sys.exit(255)
465
466        # オプションセクションチェック
467        option_sections = [
468            "results",
469            "graph",
470            "ranking",
471            "report",
472            "alias",
473            "member",
474            "team",
475            "comment",
476            "regulations",
477        ]
478        for x in option_sections:
479            if x not in self._parser.sections():
480                self._parser.add_section(x)
481
482        # set base directory
483        self.script_dir = Path(sys.argv[0]).absolute().parent
484        """スクリプトが保存されているディレクトリパス"""
485        self.config_dir = _config.absolute().parent
486        """設定ファイルが保存されているディレクトリパス"""
487
488        # 設定値取り込み
489        self.setting = SettingSection(self, "setting")
490        """settingセクション設定値"""
491        self.mahjong = MahjongSection(self, "mahjong")
492        """mahjongセクション設定値"""
493        self.member = MemberSection(self, "member")
494        """memberセクション設定値"""
495        self.team = TeamSection(self, "team")
496        """teamセクション設定値"""
497        self.alias = AliasSection(self, "alias")
498        """aliasセクション設定値"""
499        self.comment = CommentSection(self, "comment")
500        """commentセクション設定値"""
501        self.dropitems = DropItems(self)  # 非表示項目
502        """非表示項目"""
503        self.badge = BadgeDisplay(self)  # バッジ表示
504        """バッジ設定"""
505
506        # サブコマンド
507        self.results = SubCommand(self, "results", "麻雀成績")
508        """resultsセクション設定値"""
509        self.graph = SubCommand(self, "graph", "麻雀グラフ")
510        """graphセクション設定値"""
511        self.ranking = SubCommand(self, "ranking", "麻雀ランキング")
512        """rankingセクション設定値"""
513        self.report = SubCommand(self, "report", "麻雀成績レポート")
514        """reportセクション設定値"""
515
516        # 共通設定値
517        self.undefined_word: int = 0
518        """レギュレーションワードテーブルに登録されていないワードの種別"""
519        self.aggregate_unit: Literal["A", "M", "Y", None] = None
520        """レポート生成用日付範囲デフォルト値(レポート生成用)
521        - *A*: 全期間
522        - *M*: 月別
523        - *Y*: 年別
524        - *None*: 未定義
525        """
526
527    def word_list(self) -> list:
528        """設定されている値、キーワードをリスト化する
529
530        Returns:
531            list: リスト化されたキーワード
532        """
533
534        words: list = [
535            [self.setting.keyword],
536            [self.setting.remarks_word],
537            self.results.commandword,
538            self.graph.commandword,
539            self.ranking.commandword,
540            self.report.commandword,
541        ]
542
543        for k, v in self.alias.to_dict().items():
544            if isinstance(v, list):
545                words.append([k])
546                words.append(v)
547
548        words = list(set(chain.from_iterable(words)))  # 重複排除/平滑化
549        words = [x for x in words if x != ""]  # 空文字削除
550
551        return words

コンフィグ解析クラス

AppConfig(config_file: str)
452    def __init__(self, config_file: str):
453        _config = Path(config_file)
454        try:
455            self._parser = ConfigParser()
456            self._parser.read(_config, encoding="utf-8")
457        except Exception as err:
458            raise RuntimeError(err) from err
459
460        # 必須セクションチェック
461        for x in ("mahjong", "setting"):
462            if x not in self._parser.sections():
463                logging.critical("Required section not found. (%s)", x)
464                sys.exit(255)
465
466        # オプションセクションチェック
467        option_sections = [
468            "results",
469            "graph",
470            "ranking",
471            "report",
472            "alias",
473            "member",
474            "team",
475            "comment",
476            "regulations",
477        ]
478        for x in option_sections:
479            if x not in self._parser.sections():
480                self._parser.add_section(x)
481
482        # set base directory
483        self.script_dir = Path(sys.argv[0]).absolute().parent
484        """スクリプトが保存されているディレクトリパス"""
485        self.config_dir = _config.absolute().parent
486        """設定ファイルが保存されているディレクトリパス"""
487
488        # 設定値取り込み
489        self.setting = SettingSection(self, "setting")
490        """settingセクション設定値"""
491        self.mahjong = MahjongSection(self, "mahjong")
492        """mahjongセクション設定値"""
493        self.member = MemberSection(self, "member")
494        """memberセクション設定値"""
495        self.team = TeamSection(self, "team")
496        """teamセクション設定値"""
497        self.alias = AliasSection(self, "alias")
498        """aliasセクション設定値"""
499        self.comment = CommentSection(self, "comment")
500        """commentセクション設定値"""
501        self.dropitems = DropItems(self)  # 非表示項目
502        """非表示項目"""
503        self.badge = BadgeDisplay(self)  # バッジ表示
504        """バッジ設定"""
505
506        # サブコマンド
507        self.results = SubCommand(self, "results", "麻雀成績")
508        """resultsセクション設定値"""
509        self.graph = SubCommand(self, "graph", "麻雀グラフ")
510        """graphセクション設定値"""
511        self.ranking = SubCommand(self, "ranking", "麻雀ランキング")
512        """rankingセクション設定値"""
513        self.report = SubCommand(self, "report", "麻雀成績レポート")
514        """reportセクション設定値"""
515
516        # 共通設定値
517        self.undefined_word: int = 0
518        """レギュレーションワードテーブルに登録されていないワードの種別"""
519        self.aggregate_unit: Literal["A", "M", "Y", None] = None
520        """レポート生成用日付範囲デフォルト値(レポート生成用)
521        - *A*: 全期間
522        - *M*: 月別
523        - *Y*: 年別
524        - *None*: 未定義
525        """
script_dir

スクリプトが保存されているディレクトリパス

config_dir

設定ファイルが保存されているディレクトリパス

setting

settingセクション設定値

mahjong

mahjongセクション設定値

member

memberセクション設定値

team

teamセクション設定値

alias

aliasセクション設定値

comment

commentセクション設定値

dropitems

非表示項目

badge

バッジ設定

results

resultsセクション設定値

graph

graphセクション設定値

ranking

rankingセクション設定値

report

reportセクション設定値

undefined_word: int

レギュレーションワードテーブルに登録されていないワードの種別

aggregate_unit: Literal['A', 'M', 'Y', None]

レポート生成用日付範囲デフォルト値(レポート生成用)

  • A: 全期間
  • M: 月別
  • Y: 年別
  • None: 未定義
def word_list(self) -> list:
527    def word_list(self) -> list:
528        """設定されている値、キーワードをリスト化する
529
530        Returns:
531            list: リスト化されたキーワード
532        """
533
534        words: list = [
535            [self.setting.keyword],
536            [self.setting.remarks_word],
537            self.results.commandword,
538            self.graph.commandword,
539            self.ranking.commandword,
540            self.report.commandword,
541        ]
542
543        for k, v in self.alias.to_dict().items():
544            if isinstance(v, list):
545                words.append([k])
546                words.append(v)
547
548        words = list(set(chain.from_iterable(words)))  # 重複排除/平滑化
549        words = [x for x in words if x != ""]  # 空文字削除
550
551        return words

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

Returns:

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