StrictDoc Documentation
strictdoc/cli/cli_arg_parser.py
Source file coverage
Path:
strictdoc/cli/cli_arg_parser.py
Lines:
134
Non-empty lines:
114
Non-empty lines covered with requirements:
114 / 114 (100.0%)
Functions:
10
Functions covered by requirements:
10 / 10 (100.0%)
1
import argparse
2
import sys
3
from typing import Any, Dict, NoReturn
4
 
5
from strictdoc import __version__
6
from strictdoc.helpers.cast import assert_cast
7
from strictdoc.helpers.parallelizer import Parallelizer
8
 
9
 
10
def formatter(prog: str) -> argparse.RawTextHelpFormatter:
11
    return argparse.RawTextHelpFormatter(
12
        prog, indent_increment=2, max_help_position=4, width=80
13
    )
14
 
15
 
16
class SDocArgumentParser(argparse.ArgumentParser):
17
    def error(self, message: str) -> NoReturn:
18
        self.print_usage(sys.stderr)
19
        print(f"{self.prog}: error: {message}", file=sys.stderr)  # noqa: T201
20
        print("")  # noqa: T201
21
        print("Further help:")  # noqa: T201
22
        print(  # noqa: T201
23
            "'strictdoc -h/--help' provides a general overview of available commands."
24
        )
25
        print(  # noqa: T201
26
            "'strictdoc <command> -h/--help' provides command-specific help."
27
        )
28
        sys.exit(2)
29
 
30
 
31
class SDocArgsParser:
32
    @classmethod
33
    def create_sdoc_args_parser(
34
        cls, registry: Dict[str, Any]
35
    ) -> "SDocArgsParser":
36
        parser = cls.build_argparse(registry)
37
        args = parser.parse_args()
38
        return cls(args, registry)
39
 
40
    def __init__(self, args: argparse.Namespace, registry: Dict[str, Any]):
41
        self.args: argparse.Namespace = args
42
        self.registry: Dict[str, Any] = registry
43
 
44
    def is_debug_mode(self) -> bool:
45
        return assert_cast(self.args.debug, bool)
46
 
47
    def is_development_mode(self) -> bool:
48
        return assert_cast(self.args.development, bool)
49
 
50
    def run(self, parallelizer: Parallelizer) -> bool:
51
        if self.args.command not in self.registry:
52
            return False
53
 
54
        cmd = self.registry[self.args.command]
55
        if isinstance(cmd, dict):
56
            assert self.args.subcommand in cmd
57
            command_instance = cmd[self.args.subcommand](self.args)
58
        else:
59
            command_instance = cmd(self.args)
60
        command_instance.run(parallelizer)
61
 
62
        return True
63
 
64
    @classmethod
65
    def build_argparse(cls, registry: Dict[str, Any]) -> SDocArgumentParser:
66
        # https://stackoverflow.com/a/19476216/598057
67
        main_parser = SDocArgumentParser(
68
            prog="strictdoc",
69
            add_help=True,
70
            epilog=(
71
                """
72
                Further help: https://strictdoc.readthedocs.io/en/stable/
73
                """
74
            ),
75
        )
76
 
77
        # The -v/--version has a special behavior that it still works when all
78
        # commands are required == True.
79
        # https://stackoverflow.com/a/12123598/598057
80
        main_parser.add_argument(
81
            "-v", "--version", action="version", version=__version__
82
        )
83
 
84
        main_parser.add_argument(
85
            "--debug",
86
            action="store_true",
87
            default=False,
88
            help="Enable more verbose printing of errors when they are encountered.",
89
        )
90
 
91
        # Marks a StrictDoc-team-internal development instance (e.g. our own
92
        # `invoke server`), independent of --debug: --debug is about verbose
93
        # error output and is meaningful for end users too, while this flag
94
        # only identifies "this is StrictDoc's own dev server" (see
95
        # developer/tasks/20260708_favicon_vars/task.md for the favicon use
96
        # case). Hidden from --help on purpose, not for end users.
97
        main_parser.add_argument(
98
            "--development",
99
            action="store_true",
100
            default=False,
101
            help=argparse.SUPPRESS,
102
        )
103
 
104
        command_subparsers = main_parser.add_subparsers(
105
            title="command", dest="command"
106
        )
107
        command_subparsers.required = True
108
 
109
        # Dynamically add subcommands
110
        for name, cmd in registry.items():
111
            if isinstance(cmd, dict):  # command family
112
                family_parser = command_subparsers.add_parser(name)
113
                family_subparsers = family_parser.add_subparsers(
114
                    dest="subcommand"
115
                )
116
                family_subparsers.required = True
117
                for subname, subcmd in cmd.items():
118
                    sub_parser = family_subparsers.add_parser(
119
                        subname,
120
                        help=subcmd.HELP,
121
                        description=subcmd.DETAILED_HELP,
122
                        formatter_class=formatter,
123
                    )
124
                    subcmd.add_arguments(sub_parser)
125
            else:
126
                cmd_parser = command_subparsers.add_parser(
127
                    name,
128
                    help=cmd.HELP,
129
                    description=cmd.DETAILED_HELP,
130
                    formatter_class=formatter,
131
                )
132
                cmd.add_arguments(cmd_parser)
133
 
134
        return main_parser