Coverage for src / competitive_verifier / app.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-05 16:00 +0000

1import importlib.metadata 

2from argparse import ArgumentParser as BaseParser 

3from logging import getLogger 

4from typing import Annotated, Any, Literal, get_args 

5 

6from pydantic import Field, TypeAdapter 

7 

8from competitive_verifier.arg import BaseArguments 

9from competitive_verifier.documents import Docs 

10from competitive_verifier.download import Download 

11from competitive_verifier.inout import Check, MergeInput, MergeResult 

12from competitive_verifier.migrate import Migration 

13from competitive_verifier.oj import OjResolve 

14from competitive_verifier.verify import Verify 

15 

16logger = getLogger(__name__) 

17 

18 

19class HelpError(Exception): 

20 pass 

21 

22 

23class NoSubcommand(BaseArguments): 

24 subcommand: Literal[None] = None # noqa: PYI061 

25 

26 def run(self) -> bool: 

27 raise HelpError 

28 

29 @classmethod 

30 def add_parser(cls, parser: BaseParser): 

31 super().add_parser(parser) 

32 parser.add_argument( 

33 "--version", 

34 action="version", 

35 version=importlib.metadata.version("competitive-verifier"), 

36 help="print the competitive-verifier version number", 

37 ) 

38 

39 

40Arguments = Annotated[ 

41 NoSubcommand 

42 | Verify 

43 | Docs 

44 | Download 

45 | MergeInput 

46 | MergeResult 

47 | Check 

48 | OjResolve 

49 | Migration, 

50 Field(discriminator="subcommand"), 

51] 

52ARG_TYPES: tuple[type[BaseArguments], ...] = get_args(Arguments.__origin__) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue] 

53 

54 

55class ArgumentParser(BaseParser): 

56 def __init__(self, **kwargs: dict[str, Any]) -> None: 

57 super().__init__(**kwargs) # pyright: ignore[reportArgumentType] 

58 subparsers = self.add_subparsers(dest="subcommand", parser_class=BaseParser) 

59 

60 for s in ARG_TYPES: 

61 s.add_parser( 

62 self 

63 if (sub := s.get_subcommand_info()) is None 

64 else subparsers.add_parser(**sub) 

65 ) 

66 

67 def parse(self, args: list[str] | None = None) -> Arguments: 

68 type_adapter = TypeAdapter[Arguments](Arguments) 

69 return type_adapter.validate_python(self.parse_args(args).__dict__) 

70 

71 

72def main(args: list[str] | None = None) -> int | None: 

73 parser = ArgumentParser() 

74 

75 try: 

76 return not parser.parse(args).run() 

77 except HelpError: 

78 parser.print_help() 

79 return 2 

80 

81 

82if __name__ == "__main__": # pragma: no cover 

83 main()