libs.configuration

libs/configuration.py

  1"""
  2libs/configuration.py
  3"""
  4
  5import argparse
  6import logging
  7import os
  8import sys
  9from functools import partial
 10from typing import TYPE_CHECKING, cast
 11
 12import libs.commands.graph.entry
 13import libs.commands.ranking.entry
 14import libs.commands.report.entry
 15import libs.commands.results.entry
 16import libs.global_value as g
 17from cls.config import AppConfig
 18from integrations import factory
 19from libs.data import lookup
 20from libs.functions import compose
 21from libs.registry import member, team
 22from libs.types import StyleOptions
 23
 24if TYPE_CHECKING:
 25    from cls.config import SubCommand
 26    from integrations.protocols import MessageParserProtocol
 27
 28
 29def set_loglevel():
 30    """ログレベル追加"""
 31
 32    # DEBUG : 10
 33    # INFO : 20
 34    # WARNING : 30
 35    # ERROR : 40
 36    # CRITICAL : 50
 37
 38    # TRACE
 39    logging.TRACE = 5  # type: ignore
 40    logging.trace = partial(logging.log, logging.TRACE)  # type: ignore
 41    logging.addLevelName(logging.TRACE, "TRACE")  # type: ignore
 42
 43
 44def arg_parser() -> argparse.Namespace:
 45    """コマンドライン解析
 46
 47    Returns:
 48        argparse.Namespace: オブジェクト
 49    """
 50
 51    p = argparse.ArgumentParser(
 52        formatter_class=argparse.RawTextHelpFormatter,
 53        add_help=True,
 54    )
 55
 56    p.add_argument(
 57        "-c", "--config",
 58        default="config.ini",
 59        help="設定ファイル(default: %(default)s)",
 60    )
 61    p.add_argument(
 62        "--profile",
 63        help=argparse.SUPPRESS,
 64    )
 65    p.add_argument(
 66        "--service",
 67        choices=[
 68            "slack",
 69            "discord",
 70            "standard_io", "std",
 71            "web", "flask",
 72        ],
 73        default="slack",
 74        help="連携先サービス",
 75    )
 76
 77    logging_group = p.add_argument_group("logging options")
 78    logging_group.add_argument(
 79        "--debug",
 80        action="store_true",
 81        help="デバッグ情報表示",
 82    )
 83    logging_group.add_argument(
 84        "--verbose", "--trace",
 85        dest="verbose",
 86        action="store_true",
 87        help="詳細デバッグ情報表示",
 88    )
 89    logging_group.add_argument(
 90        "--moderate",
 91        action="store_true",
 92        help="ログレベルがエラー以下のもを非表示",
 93    )
 94    logging_group.add_argument(
 95        "--notime",
 96        action="store_true",
 97        help="ログフォーマットから日時を削除",
 98    )
 99
100    match os.path.basename(sys.argv[0]):
101        case "app.py":
102            service_stdio = p.add_argument_group("Only allowed when --service=standard_io")
103            service_stdio.add_argument(
104                "--text",
105                type=str,
106                help="input text strings",
107            )
108            service_web = p.add_argument_group("Only allowed when --service=web")
109            service_web.add_argument(
110                "--host",
111                type=str,
112                default="127.0.0.1",
113                help="listen  address(default: %(default)s)",
114            )
115            service_web.add_argument(
116                "--port",
117                type=int,
118                default=8000,
119                help="bind port(default: %(default)s)",
120            )
121        case "dbtools.py":  # dbtools専用オプション
122            required = p.add_argument_group("Required options(amutually exclusive)")
123            exclusive = required.add_mutually_exclusive_group()
124            exclusive.add_argument(
125                "--compar",
126                action="store_true",
127                help="データ突合",
128            )
129            exclusive.add_argument(
130                "--unification",
131                nargs="?",
132                const="rename.ini",
133                help="ファイルの内容に従って記録済みのメンバー名を修正する(default: %(const)s)",
134            )
135            exclusive.add_argument(
136                "--recalculation",
137                action="store_true",
138                help="ポイント再計算",
139            )
140            exclusive.add_argument(
141                "--export",
142                dest="export_data",
143                nargs="?",
144                const="export",
145                metavar="PREFIX",
146                help="メンバー設定情報をエクスポート(default prefix: %(const)s)",
147            )
148            exclusive.add_argument(
149                "--import",
150                nargs="?",
151                dest="import_data",
152                const="export",
153                metavar="PREFIX",
154                help="メンバー設定情報をインポート(default prefix: %(const)s)",
155            )
156            exclusive.add_argument(
157                "--vacuum",
158                action="store_true",
159                help="database vacuum",
160            )
161            exclusive.add_argument(
162                "--gen-test-data",
163                type=int,
164                dest="gen_test_data",
165                nargs="?",
166                const=1,
167                default=None,
168                metavar="count",
169                help="テスト用サンプルデータ生成(count=生成回数, default: %(const)s)",
170            )
171        case "test.py":  # 動作テスト用オプション
172            p.add_argument(
173                "-t", "--testcase",
174                dest="testcase",
175            )
176
177    return p.parse_args()
178
179
180def setup():
181    """設定ファイル読み込み"""
182
183    set_loglevel()
184
185    g.args = arg_parser()
186
187    # 連携サービス
188    match g.args.service:
189        case "slack":
190            g.selected_service = "slack"
191        case "discord":
192            g.selected_service = "discord"
193        case "standard_io" | "std":
194            g.selected_service = "standard_io"
195        case "web" | "flask":
196            g.selected_service = "web"
197        case _:
198            sys.exit()
199
200    if not hasattr(g.args, "testcase"):
201        g.args.testcase = False
202    else:
203        g.selected_service = "standard_io"
204
205    # ログフォーマット
206    if g.args.notime:
207        fmt = ""
208    else:
209        fmt = "[%(asctime)s]"
210    if g.args.debug or g.args.verbose:
211        fmt += "[%(levelname)s][%(name)s:%(module)s:%(funcName)s] %(message)s"
212    else:
213        fmt += "[%(levelname)s][%(module)s:%(funcName)s] %(message)s"
214
215    if g.args.debug:
216        if g.args.verbose:
217            logging.basicConfig(level=logging.TRACE, format=fmt)  # type: ignore
218            logging.info("DEBUG MODE(verbose)")
219        else:
220            logging.basicConfig(level=logging.DEBUG, format=fmt)
221            logging.info("DEBUG MODE")
222    else:
223        if g.args.moderate:
224            logging.basicConfig(level=logging.WARNING, format=fmt)
225        else:
226            logging.basicConfig(level=logging.INFO, format=fmt)
227
228    g.cfg = AppConfig(g.args.config)
229    g.adapter = factory.select_adapter(g.selected_service, g.cfg)
230    register()
231
232    # 設定内容のロギング
233    logging.info("conf: %s", os.path.join(g.cfg.config_dir, g.args.config))
234    logging.info("font: %s", g.cfg.setting.font_file)
235    logging.info("database: %s", g.cfg.setting.database_file)
236    logging.info("service: %s, graph_library: %s", g.selected_service, g.adapter.conf.plotting_backend)
237    logging.info(
238        "rule_version: %s, origin_point: %s, return_point: %s, time_adjust: %sh",
239        g.cfg.mahjong.rule_version, g.cfg.mahjong.origin_point, g.cfg.mahjong.return_point, g.cfg.setting.time_adjust
240    )
241
242
243def read_memberslist(log=True):
244    """メンバー/チームリスト読み込み
245
246    Args:
247        log (bool, optional): 読み込み時に内容をログに出力する. Defaults to True.
248    """
249
250    g.cfg.member.guest_name = lookup.db.get_guest()
251    g.member_list = lookup.db.get_member_list()
252    g.team_list = lookup.db.get_team_list()
253
254    if log:
255        logging.info("guest_name: %s", g.cfg.member.guest_name)
256        logging.info("member_list: %s", sorted(set(g.member_list.values())))
257        logging.info("team_list: %s", [x["team"] for x in g.team_list])
258
259
260def register():
261    """ディスパッチテーブル登録"""
262
263    def dispatch_help(m: "MessageParserProtocol"):
264        m.set_data("ヘルプ", compose.msg_help.event_message(), StyleOptions())
265        m.post.ts = m.data.event_ts
266
267    def dispatch_download(m: "MessageParserProtocol"):
268        m.set_data("成績記録DB", g.cfg.setting.database_file, StyleOptions())
269
270    def dispatch_members_list(m: "MessageParserProtocol"):
271        m.set_data("登録済みメンバー", lookup.textdata.get_members_list(), StyleOptions(codeblock=True))
272        m.post.ts = m.data.event_ts
273
274    def dispatch_team_list(m: "MessageParserProtocol"):
275        m.set_data("登録済みチーム", lookup.textdata.get_team_list(), StyleOptions(codeblock=True))
276        m.post.ts = m.data.event_ts
277
278    def dispatch_member_append(m: "MessageParserProtocol"):
279        m.set_data("メンバー追加", member.append(m.argument), StyleOptions(key_title=False))
280
281    def dispatch_member_remove(m: "MessageParserProtocol"):
282        m.set_data("メンバー削除", member.remove(m.argument), StyleOptions(key_title=False))
283
284    def dispatch_team_create(m: "MessageParserProtocol"):
285        m.set_data("チーム作成", team.create(m.argument), StyleOptions(key_title=False))
286
287    def dispatch_team_delete(m: "MessageParserProtocol"):
288        m.set_data("チーム削除", team.delete(m.argument), StyleOptions(key_title=False))
289
290    def dispatch_team_append(m: "MessageParserProtocol"):
291        m.set_data("チーム所属", team.append(m.argument), StyleOptions(key_title=False))
292
293    def dispatch_team_remove(m: "MessageParserProtocol"):
294        m.set_data("チーム脱退", team.remove(m.argument), StyleOptions(key_title=False))
295
296    def dispatch_team_clear(m: "MessageParserProtocol"):
297        m.set_data("全チーム削除", team.clear(), StyleOptions(key_title=False))
298
299    dispatch_table: dict = {
300        "results": libs.commands.results.entry.main,
301        "graph": libs.commands.graph.entry.main,
302        "ranking": libs.commands.ranking.entry.main,
303        "report": libs.commands.report.entry.main,
304        "member": dispatch_members_list,
305        "team_list": dispatch_team_list,
306        "download": dispatch_download,
307        "add": dispatch_member_append,
308        "delete": dispatch_member_remove,
309        "team_create": dispatch_team_create,
310        "team_del": dispatch_team_delete,
311        "team_add": dispatch_team_append,
312        "team_remove": dispatch_team_remove,
313        "team_clear": dispatch_team_clear,
314    }
315
316    # ヘルプ
317    g.keyword_dispatcher.update({g.cfg.setting.help: dispatch_help})
318
319    for command, ep in dispatch_table.items():
320        # 呼び出しキーワード登録
321        if hasattr(g.cfg, command):
322            sub_command = cast("SubCommand", getattr(g.cfg, command))
323            for alias in sub_command.commandword:
324                g.keyword_dispatcher.update({alias: ep})
325        # スラッシュコマンド登録
326        for alias in cast(list, getattr(g.cfg.alias, command)):
327            g.command_dispatcher.update({alias: ep})
328
329    # サービス別コマンド登録
330    g.command_dispatcher.update(g.adapter.conf.command_dispatcher)
331    g.keyword_dispatcher.update(g.adapter.conf.keyword_dispatcher)
332
333    logging.debug("keyword_dispatcher:\n%s", "\n".join([f"\t{k}: {v}" for k, v in g.keyword_dispatcher.items()]))
334    logging.debug("command_dispatcher:\n%s", "\n".join([f"\t{k}: {v}" for k, v in g.command_dispatcher.items()]))
def set_loglevel():
30def set_loglevel():
31    """ログレベル追加"""
32
33    # DEBUG : 10
34    # INFO : 20
35    # WARNING : 30
36    # ERROR : 40
37    # CRITICAL : 50
38
39    # TRACE
40    logging.TRACE = 5  # type: ignore
41    logging.trace = partial(logging.log, logging.TRACE)  # type: ignore
42    logging.addLevelName(logging.TRACE, "TRACE")  # type: ignore

ログレベル追加

def arg_parser() -> argparse.Namespace:
 45def arg_parser() -> argparse.Namespace:
 46    """コマンドライン解析
 47
 48    Returns:
 49        argparse.Namespace: オブジェクト
 50    """
 51
 52    p = argparse.ArgumentParser(
 53        formatter_class=argparse.RawTextHelpFormatter,
 54        add_help=True,
 55    )
 56
 57    p.add_argument(
 58        "-c", "--config",
 59        default="config.ini",
 60        help="設定ファイル(default: %(default)s)",
 61    )
 62    p.add_argument(
 63        "--profile",
 64        help=argparse.SUPPRESS,
 65    )
 66    p.add_argument(
 67        "--service",
 68        choices=[
 69            "slack",
 70            "discord",
 71            "standard_io", "std",
 72            "web", "flask",
 73        ],
 74        default="slack",
 75        help="連携先サービス",
 76    )
 77
 78    logging_group = p.add_argument_group("logging options")
 79    logging_group.add_argument(
 80        "--debug",
 81        action="store_true",
 82        help="デバッグ情報表示",
 83    )
 84    logging_group.add_argument(
 85        "--verbose", "--trace",
 86        dest="verbose",
 87        action="store_true",
 88        help="詳細デバッグ情報表示",
 89    )
 90    logging_group.add_argument(
 91        "--moderate",
 92        action="store_true",
 93        help="ログレベルがエラー以下のもを非表示",
 94    )
 95    logging_group.add_argument(
 96        "--notime",
 97        action="store_true",
 98        help="ログフォーマットから日時を削除",
 99    )
100
101    match os.path.basename(sys.argv[0]):
102        case "app.py":
103            service_stdio = p.add_argument_group("Only allowed when --service=standard_io")
104            service_stdio.add_argument(
105                "--text",
106                type=str,
107                help="input text strings",
108            )
109            service_web = p.add_argument_group("Only allowed when --service=web")
110            service_web.add_argument(
111                "--host",
112                type=str,
113                default="127.0.0.1",
114                help="listen  address(default: %(default)s)",
115            )
116            service_web.add_argument(
117                "--port",
118                type=int,
119                default=8000,
120                help="bind port(default: %(default)s)",
121            )
122        case "dbtools.py":  # dbtools専用オプション
123            required = p.add_argument_group("Required options(amutually exclusive)")
124            exclusive = required.add_mutually_exclusive_group()
125            exclusive.add_argument(
126                "--compar",
127                action="store_true",
128                help="データ突合",
129            )
130            exclusive.add_argument(
131                "--unification",
132                nargs="?",
133                const="rename.ini",
134                help="ファイルの内容に従って記録済みのメンバー名を修正する(default: %(const)s)",
135            )
136            exclusive.add_argument(
137                "--recalculation",
138                action="store_true",
139                help="ポイント再計算",
140            )
141            exclusive.add_argument(
142                "--export",
143                dest="export_data",
144                nargs="?",
145                const="export",
146                metavar="PREFIX",
147                help="メンバー設定情報をエクスポート(default prefix: %(const)s)",
148            )
149            exclusive.add_argument(
150                "--import",
151                nargs="?",
152                dest="import_data",
153                const="export",
154                metavar="PREFIX",
155                help="メンバー設定情報をインポート(default prefix: %(const)s)",
156            )
157            exclusive.add_argument(
158                "--vacuum",
159                action="store_true",
160                help="database vacuum",
161            )
162            exclusive.add_argument(
163                "--gen-test-data",
164                type=int,
165                dest="gen_test_data",
166                nargs="?",
167                const=1,
168                default=None,
169                metavar="count",
170                help="テスト用サンプルデータ生成(count=生成回数, default: %(const)s)",
171            )
172        case "test.py":  # 動作テスト用オプション
173            p.add_argument(
174                "-t", "--testcase",
175                dest="testcase",
176            )
177
178    return p.parse_args()

コマンドライン解析

Returns:

argparse.Namespace: オブジェクト

def setup():
181def setup():
182    """設定ファイル読み込み"""
183
184    set_loglevel()
185
186    g.args = arg_parser()
187
188    # 連携サービス
189    match g.args.service:
190        case "slack":
191            g.selected_service = "slack"
192        case "discord":
193            g.selected_service = "discord"
194        case "standard_io" | "std":
195            g.selected_service = "standard_io"
196        case "web" | "flask":
197            g.selected_service = "web"
198        case _:
199            sys.exit()
200
201    if not hasattr(g.args, "testcase"):
202        g.args.testcase = False
203    else:
204        g.selected_service = "standard_io"
205
206    # ログフォーマット
207    if g.args.notime:
208        fmt = ""
209    else:
210        fmt = "[%(asctime)s]"
211    if g.args.debug or g.args.verbose:
212        fmt += "[%(levelname)s][%(name)s:%(module)s:%(funcName)s] %(message)s"
213    else:
214        fmt += "[%(levelname)s][%(module)s:%(funcName)s] %(message)s"
215
216    if g.args.debug:
217        if g.args.verbose:
218            logging.basicConfig(level=logging.TRACE, format=fmt)  # type: ignore
219            logging.info("DEBUG MODE(verbose)")
220        else:
221            logging.basicConfig(level=logging.DEBUG, format=fmt)
222            logging.info("DEBUG MODE")
223    else:
224        if g.args.moderate:
225            logging.basicConfig(level=logging.WARNING, format=fmt)
226        else:
227            logging.basicConfig(level=logging.INFO, format=fmt)
228
229    g.cfg = AppConfig(g.args.config)
230    g.adapter = factory.select_adapter(g.selected_service, g.cfg)
231    register()
232
233    # 設定内容のロギング
234    logging.info("conf: %s", os.path.join(g.cfg.config_dir, g.args.config))
235    logging.info("font: %s", g.cfg.setting.font_file)
236    logging.info("database: %s", g.cfg.setting.database_file)
237    logging.info("service: %s, graph_library: %s", g.selected_service, g.adapter.conf.plotting_backend)
238    logging.info(
239        "rule_version: %s, origin_point: %s, return_point: %s, time_adjust: %sh",
240        g.cfg.mahjong.rule_version, g.cfg.mahjong.origin_point, g.cfg.mahjong.return_point, g.cfg.setting.time_adjust
241    )

設定ファイル読み込み

def read_memberslist(log=True):
244def read_memberslist(log=True):
245    """メンバー/チームリスト読み込み
246
247    Args:
248        log (bool, optional): 読み込み時に内容をログに出力する. Defaults to True.
249    """
250
251    g.cfg.member.guest_name = lookup.db.get_guest()
252    g.member_list = lookup.db.get_member_list()
253    g.team_list = lookup.db.get_team_list()
254
255    if log:
256        logging.info("guest_name: %s", g.cfg.member.guest_name)
257        logging.info("member_list: %s", sorted(set(g.member_list.values())))
258        logging.info("team_list: %s", [x["team"] for x in g.team_list])

メンバー/チームリスト読み込み

Arguments:
  • log (bool, optional): 読み込み時に内容をログに出力する. Defaults to True.
def register():
261def register():
262    """ディスパッチテーブル登録"""
263
264    def dispatch_help(m: "MessageParserProtocol"):
265        m.set_data("ヘルプ", compose.msg_help.event_message(), StyleOptions())
266        m.post.ts = m.data.event_ts
267
268    def dispatch_download(m: "MessageParserProtocol"):
269        m.set_data("成績記録DB", g.cfg.setting.database_file, StyleOptions())
270
271    def dispatch_members_list(m: "MessageParserProtocol"):
272        m.set_data("登録済みメンバー", lookup.textdata.get_members_list(), StyleOptions(codeblock=True))
273        m.post.ts = m.data.event_ts
274
275    def dispatch_team_list(m: "MessageParserProtocol"):
276        m.set_data("登録済みチーム", lookup.textdata.get_team_list(), StyleOptions(codeblock=True))
277        m.post.ts = m.data.event_ts
278
279    def dispatch_member_append(m: "MessageParserProtocol"):
280        m.set_data("メンバー追加", member.append(m.argument), StyleOptions(key_title=False))
281
282    def dispatch_member_remove(m: "MessageParserProtocol"):
283        m.set_data("メンバー削除", member.remove(m.argument), StyleOptions(key_title=False))
284
285    def dispatch_team_create(m: "MessageParserProtocol"):
286        m.set_data("チーム作成", team.create(m.argument), StyleOptions(key_title=False))
287
288    def dispatch_team_delete(m: "MessageParserProtocol"):
289        m.set_data("チーム削除", team.delete(m.argument), StyleOptions(key_title=False))
290
291    def dispatch_team_append(m: "MessageParserProtocol"):
292        m.set_data("チーム所属", team.append(m.argument), StyleOptions(key_title=False))
293
294    def dispatch_team_remove(m: "MessageParserProtocol"):
295        m.set_data("チーム脱退", team.remove(m.argument), StyleOptions(key_title=False))
296
297    def dispatch_team_clear(m: "MessageParserProtocol"):
298        m.set_data("全チーム削除", team.clear(), StyleOptions(key_title=False))
299
300    dispatch_table: dict = {
301        "results": libs.commands.results.entry.main,
302        "graph": libs.commands.graph.entry.main,
303        "ranking": libs.commands.ranking.entry.main,
304        "report": libs.commands.report.entry.main,
305        "member": dispatch_members_list,
306        "team_list": dispatch_team_list,
307        "download": dispatch_download,
308        "add": dispatch_member_append,
309        "delete": dispatch_member_remove,
310        "team_create": dispatch_team_create,
311        "team_del": dispatch_team_delete,
312        "team_add": dispatch_team_append,
313        "team_remove": dispatch_team_remove,
314        "team_clear": dispatch_team_clear,
315    }
316
317    # ヘルプ
318    g.keyword_dispatcher.update({g.cfg.setting.help: dispatch_help})
319
320    for command, ep in dispatch_table.items():
321        # 呼び出しキーワード登録
322        if hasattr(g.cfg, command):
323            sub_command = cast("SubCommand", getattr(g.cfg, command))
324            for alias in sub_command.commandword:
325                g.keyword_dispatcher.update({alias: ep})
326        # スラッシュコマンド登録
327        for alias in cast(list, getattr(g.cfg.alias, command)):
328            g.command_dispatcher.update({alias: ep})
329
330    # サービス別コマンド登録
331    g.command_dispatcher.update(g.adapter.conf.command_dispatcher)
332    g.keyword_dispatcher.update(g.adapter.conf.keyword_dispatcher)
333
334    logging.debug("keyword_dispatcher:\n%s", "\n".join([f"\t{k}: {v}" for k, v in g.keyword_dispatcher.items()]))
335    logging.debug("command_dispatcher:\n%s", "\n".join([f"\t{k}: {v}" for k, v in g.command_dispatcher.items()]))

ディスパッチテーブル登録