StrictDoc Documentation
strictdoc/cli/main.py
Source file coverage
Path:
strictdoc/cli/main.py
Lines:
125
Non-empty lines:
104
Non-empty lines covered with requirements:
104 / 104 (100.0%)
Functions:
3
Functions covered by requirements:
3 / 3 (100.0%)
1
# Needed to ensure that multiprocessing.freeze_support() is called
2
# in a frozen application (see main() below).
3
import multiprocessing
4
import sys
5
from typing import Any, Dict, Optional
6
 
7
from strictdoc import environment
8
from strictdoc.cli.cli_arg_parser import (
9
    SDocArgsParser,
10
)
11
from strictdoc.commands.about_command import AboutCommand
12
from strictdoc.commands.export import ExportCommand
13
from strictdoc.commands.import_excel import ImportExcelCommand
14
from strictdoc.commands.import_reqif import ImportReqIFCommand
15
from strictdoc.commands.launcher_command import (
16
    LauncherCommand,
17
    is_launcher_available,
18
)
19
from strictdoc.commands.manage_autouid_command import ManageAutoUIDCommand
20
from strictdoc.commands.new_command import NewCommand
21
from strictdoc.commands.server import ServerCommand
22
from strictdoc.commands.version_command import VersionCommand
23
from strictdoc.helpers.coverage import register_code_coverage_hook
24
from strictdoc.helpers.exception import (
25
    ExceptionInfo,
26
    StrictDocChildProcessException,
27
)
28
from strictdoc.helpers.parallelizer import Parallelizer
29
from strictdoc.helpers.timing import SimpleNominalExit, measure_performance
30
 
31
 
32
def create_command_registry() -> Dict[str, Any]:
33
    command_registry: Dict[str, Any] = {
34
        "about": AboutCommand,
35
        "export": ExportCommand,
36
        "import": {"excel": ImportExcelCommand, "reqif": ImportReqIFCommand},
37
        "manage": {"auto-uid": ManageAutoUIDCommand},
38
        "new": NewCommand,
39
        "server": ServerCommand,
40
        "version": VersionCommand,
41
    }
42
 
43
    if is_launcher_available():
44
        command_registry["launcher"] = LauncherCommand
45
 
46
    return command_registry
47
 
48
 
49
COMMAND_REGISTRY: Dict[str, Any] = create_command_registry()
50
 
51
 
52
def _main() -> None:
53
    # The parser can raise when no arguments or incorrect arguments are provided.
54
    try:
55
        parser = SDocArgsParser.create_sdoc_args_parser(COMMAND_REGISTRY)
56
    except Exception as exception_:
57
        print(f"error: {str(exception_)}", flush=True)  # noqa: T201
58
        sys.exit(1)
59
 
60
    if parser.is_debug_mode():
61
        environment.is_debug_mode = True
62
 
63
    # Ensure that multiprocessing.freeze_support() is called in a frozen
64
    # application
65
    # https://github.com/pyinstaller/pyinstaller/issues/7438
66
    if getattr(sys, "frozen", False):  # pragma: no cover
67
        multiprocessing.freeze_support()
68
 
69
    # This is crucial for a good performance on macOS. Linux uses 'fork' by default.
70
    # Changed in version 3.8: On macOS, the spawn start method is now the default.
71
    # The fork start method should be considered unsafe as it can lead to crashes
72
    # of the subprocess as macOS system libraries may start threads. See bpo-33725.
73
    # https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
74
    # 2024-12-26: StrictDoc has been working with 'fork' just fine, so keep doing it until
75
    #             anything serious appears against using it.
76
    if sys.platform != "win32":
77
        multiprocessing.set_start_method("fork", force=True)
78
    else:  # pragma: no cover
79
        pass  # pragma: no cover
80
 
81
    # How to make python 3 print() utf8
82
    # https://stackoverflow.com/a/3597849/598057
83
    # sys.stdout.reconfigure(encoding='utf-8') for Python 3.7
84
    sys.stdout = open(  # pylint: disable=bad-option-value,consider-using-with
85
        1, "w", encoding="utf-8", closefd=False
86
    )
87
 
88
    register_code_coverage_hook()
89
 
90
    enable_parallelization = "--no-parallelization" not in sys.argv
91
    parallelizer = Parallelizer.create(enable_parallelization)
92
 
93
    exception_info: Optional[ExceptionInfo] = None
94
    try:
95
        parser.run(parallelizer)
96
    except SimpleNominalExit:
97
        raise
98
    except StrictDocChildProcessException as exception_info_:
99
        exception_info = exception_info_.exception_info
100
    except Exception as exception_:
101
        exception_info = ExceptionInfo(exception_)
102
    finally:
103
        parallelizer.shutdown()
104
 
105
        if exception_info is not None:
106
            if parser.is_debug_mode():
107
                print(exception_info.get_stack_trace(), flush=True)  # noqa: T201
108
            print(exception_info.get_detailed_error_message(), flush=True)  # noqa: T201
109
            if not parser.is_debug_mode():
110
                print(  # noqa: T201
111
                    "Rerun with strictdoc --debug <...> to enable stack trace printing.",
112
                    flush=True,
113
                )
114
            sys.exit(1)
115
 
116
 
117
def main() -> None:
118
    with measure_performance("Total execution time"):
119
        _main()
120
 
121
 
122
if __name__ == "__main__":
123
    main()
124
else:  # pragma: no cover
125
    pass