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