Coverage for src / competitive_verifier / arg.py: 100%
82 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-05 16:00 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-05 16:00 +0000
1import logging
2import os
3import pathlib
4from abc import ABC, abstractmethod
5from argparse import ArgumentParser
6from logging import getLogger
7from typing import TYPE_CHECKING, TypedDict, cast, get_args
9from pydantic import BaseModel, Field
11from competitive_verifier import github, summary
13from .log import GitHubMessageParams, configure_stderr_logging
15if TYPE_CHECKING:
16 from competitive_verifier.models import VerifyCommandResult
18COMPETITIVE_VERIFY_FILES_PATH = "COMPETITIVE_VERIFY_FILES_PATH"
20logger = getLogger(__name__)
23class _SubcommandInfo(TypedDict):
24 name: str
25 help: str | None
28class BaseArguments(ABC, BaseModel):
29 model_config = {"extra": "ignore"}
31 @classmethod
32 def get_subcommand_info(cls) -> _SubcommandInfo | None:
33 f = cls.model_fields.get("subcommand")
34 if f is None or (subcommand := get_args(f.annotation)[0]) is None:
35 return None
37 return {"name": cast("str", subcommand), "help": f.description}
39 @abstractmethod
40 def run(self) -> bool: ...
42 @classmethod
43 def add_parser(cls, parser: ArgumentParser):
44 pass
47class VerifyFilesJsonArgumentsMixin(BaseArguments):
48 @classmethod
49 def _required(cls) -> bool:
50 return True
52 @classmethod
53 def add_parser(cls, parser: ArgumentParser):
54 super().add_parser(parser)
56 default = os.getenv(COMPETITIVE_VERIFY_FILES_PATH) if cls._required() else None
57 parser.add_argument(
58 "--verify-json",
59 dest="verify_files_json",
60 default=default or None,
61 required=cls._required() and not bool(default),
62 help="File path of verify_files.json. default: environ variable $COMPETITIVE_VERIFY_FILES_PATH",
63 type=pathlib.Path,
64 )
67class VerifyFilesJsonArguments(VerifyFilesJsonArgumentsMixin):
68 verify_files_json: pathlib.Path
71class OptionalVerifyFilesJsonArguments(VerifyFilesJsonArgumentsMixin):
72 verify_files_json: pathlib.Path | None
74 @classmethod
75 def _required(cls) -> bool:
76 return False
79class ResultJsonArguments(BaseArguments):
80 result_json: list[pathlib.Path]
82 @classmethod
83 def add_parser(cls, parser: ArgumentParser):
84 super().add_parser(parser)
85 parser.add_argument(
86 "result_json",
87 nargs="+",
88 help="Json files which is result of `verify`",
89 type=pathlib.Path,
90 )
93class IgnoreErrorArguments(BaseArguments):
94 ignore_error: bool = True
96 @classmethod
97 def add_parser(cls, parser: ArgumentParser):
98 super().add_parser(parser)
99 parser.add_argument(
100 "--check-error",
101 help="Exit not zero if failed verification exists",
102 dest="ignore_error",
103 action="store_false",
104 )
107class WriteSummaryArguments(BaseArguments):
108 write_summary: bool = False
110 @classmethod
111 def add_parser(cls, parser: ArgumentParser):
112 super().add_parser(parser)
113 parser.add_argument(
114 "--write-summary",
115 action="store_true",
116 help="Write GitHub Actions summary",
117 )
119 def write_result(self, result: "VerifyCommandResult"):
120 if self.write_summary:
121 gh_summary_path = github.env.get_step_summary_path()
122 if gh_summary_path and gh_summary_path.parent.exists():
123 with gh_summary_path.open("w", encoding="utf-8") as fp:
124 summary.write_summary(fp, result)
125 else:
126 logger.warning(
127 "write_summary=True but not found $GITHUB_STEP_SUMMARY",
128 extra={"github": GitHubMessageParams()},
129 )
132class IncludeExcludeArguments(BaseArguments):
133 include: list[str] = Field(default_factory=list)
134 exclude: list[str] = Field(default_factory=list)
136 @classmethod
137 def add_parser(cls, parser: ArgumentParser):
138 super().add_parser(parser)
139 parser.add_argument(
140 "--include",
141 nargs="*",
142 help="Included file",
143 default=[],
144 type=str,
145 )
146 parser.add_argument(
147 "--exclude",
148 nargs="*",
149 help="Excluded file",
150 default=[],
151 type=str,
152 )
155class VerboseArguments(BaseArguments):
156 verbose: bool = False
158 @abstractmethod
159 def _run(self) -> bool: ...
160 def run(self) -> bool:
161 default_level = logging.INFO
162 if self.verbose:
163 default_level = logging.DEBUG
164 configure_stderr_logging(default_level)
165 return self._run()
167 @classmethod
168 def add_parser(cls, parser: ArgumentParser):
169 super().add_parser(parser)
170 parser.add_argument(
171 "-v", "--verbose", action="store_true", help="Show debug level log."
172 )