Skip to content

Commit c6b4129

Browse files
committed
cli/main: Environment class to encapsulate all compile-time information
1 parent 2e960dd commit c6b4129

File tree

19 files changed

+280
-142
lines changed

19 files changed

+280
-142
lines changed

.github/workflows/periodic-integration-test-macos.yml

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ jobs:
2727
run: |
2828
invoke release-local
2929
30-
- name: Build and test using PyInstaller
31-
run: |
32-
invoke release-pyinstaller
33-
invoke test-integration --strictdoc /tmp/strictdoc/strictdoc/strictdoc
30+
# BROKEN:
31+
# - name: Build and test using PyInstaller
32+
# run: |
33+
# invoke release-pyinstaller
34+
# invoke test-integration --strictdoc /tmp/strictdoc/strictdoc/strictdoc

pyproject.toml

+5
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ development = [
117117
# psutil is needed to reap Uvicorn's zombie processes when running end2end
118118
# tests. One day someone finds a better solution.
119119
"psutil",
120+
121+
# Nuitka:WARNING: Using very slow fallback for ordered sets, please install
122+
# 'ordered-set' PyPI package for best Python compile time performance.
123+
"nuitka",
124+
"ordered-set",
120125
]
121126

122127
[project.scripts]

strictdoc/__init__.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import os
1+
from strictdoc.core.environment import SDocRuntimeEnvironment
22

33
__version__ = "0.0.32"
44

5-
STRICTDOC_ROOT_PATH = os.path.abspath(
6-
os.path.join(os.path.dirname(__file__), "..")
7-
)
8-
assert os.path.isabs(STRICTDOC_ROOT_PATH), f"{STRICTDOC_ROOT_PATH}"
5+
6+
environment = SDocRuntimeEnvironment(__file__)

strictdoc/cli/cli_arg_parser.py

+28-24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from enum import Enum
55
from typing import List, Optional
66

7+
from strictdoc.core.environment import SDocRuntimeEnvironment
78
from strictdoc.core.project_config import ProjectConfig
89

910
EXPORT_FORMATS = ["html", "html-standalone", "rst", "excel", "reqif-sdoc"]
@@ -281,8 +282,16 @@ def __init__(self, input_file, output_file):
281282

282283

283284
class ServerCommandConfig:
284-
def __init__(self, *, input_path: str, output_path: str, reload: bool):
285+
def __init__(
286+
self,
287+
*,
288+
environment: SDocRuntimeEnvironment,
289+
input_path: str,
290+
output_path: str,
291+
reload: bool,
292+
):
285293
assert os.path.exists(input_path)
294+
self.environment: SDocRuntimeEnvironment = environment
286295
abs_input_path = os.path.abspath(input_path)
287296
self.input_path: str = abs_input_path
288297
self.output_path: str = output_path
@@ -298,7 +307,7 @@ class ExportMode(Enum):
298307
class ExportCommandConfig: # pylint: disable=too-many-instance-attributes
299308
def __init__( # pylint: disable=too-many-arguments
300309
self,
301-
strictdoc_root_path,
310+
environment: SDocRuntimeEnvironment,
302311
input_paths,
303312
output_dir: str,
304313
project_title: Optional[str],
@@ -309,7 +318,7 @@ def __init__( # pylint: disable=too-many-arguments
309318
experimental_enable_file_traceability,
310319
):
311320
assert isinstance(input_paths, list), f"{input_paths}"
312-
self.strictdoc_root_path = strictdoc_root_path
321+
self.environment: SDocRuntimeEnvironment = environment
313322
self.input_paths: List[str] = input_paths
314323
self.output_dir: str = output_dir
315324
self.project_title: Optional[str] = project_title
@@ -333,27 +342,15 @@ def get_export_mode(self):
333342
return ExportMode.STANDALONE
334343
raise NotImplementedError
335344

345+
@property
346+
def strictdoc_root_path(self):
347+
return self.environment.path_to_strictdoc
348+
336349
def get_static_files_path(self):
337-
if getattr(sys, "frozen", False):
338-
# If the application is run as a bundle, the PyInstaller bootloader
339-
# extends the sys module by a flag frozen=True and sets the app
340-
# path into variable _MEIPASS'.
341-
bundle_dir = sys._MEIPASS # pylint: disable=protected-access
342-
return os.path.join(bundle_dir, "_static")
343-
return os.path.join(
344-
self.strictdoc_root_path, "strictdoc/export/html/_static"
345-
)
350+
return self.environment.get_static_files_path()
346351

347352
def get_extra_static_files_path(self):
348-
if getattr(sys, "frozen", False):
349-
# If the application is run as a bundle, the PyInstaller bootloader
350-
# extends the sys module by a flag frozen=True and sets the app
351-
# path into variable _MEIPASS'.
352-
bundle_dir = sys._MEIPASS # pylint: disable=protected-access
353-
return os.path.join(bundle_dir, "_static_extra")
354-
return os.path.join(
355-
self.strictdoc_root_path, "strictdoc/export/html/_static_extra"
356-
)
353+
return self.environment.get_extra_static_files_path()
357354

358355
def integrate_project_config(self, project_config: ProjectConfig):
359356
if self.project_title is None:
@@ -410,7 +407,10 @@ def get_passthrough_config(self) -> PassthroughCommandConfig:
410407
self.args.input_file, self.args.output_file
411408
)
412409

413-
def get_export_config(self, strictdoc_root_path) -> ExportCommandConfig:
410+
def get_export_config(
411+
self, environment: SDocRuntimeEnvironment
412+
) -> ExportCommandConfig:
413+
assert isinstance(environment, SDocRuntimeEnvironment)
414414
project_title: Optional[str] = self.args.project_title
415415

416416
output_dir = self.args.output_dir if self.args.output_dir else "output"
@@ -419,7 +419,7 @@ def get_export_config(self, strictdoc_root_path) -> ExportCommandConfig:
419419
output_dir = os.path.join(cwd, output_dir)
420420

421421
return ExportCommandConfig(
422-
strictdoc_root_path,
422+
environment,
423423
self.args.input_paths,
424424
output_dir,
425425
project_title,
@@ -440,8 +440,12 @@ def get_import_config_excel(self, _) -> ImportExcelCommandConfig:
440440
self.args.input_path, self.args.output_path, self.args.parser
441441
)
442442

443-
def get_server_config(self) -> ServerCommandConfig:
443+
def get_server_config(
444+
self, environment: SDocRuntimeEnvironment
445+
) -> ServerCommandConfig:
446+
assert isinstance(environment, SDocRuntimeEnvironment), environment
444447
return ServerCommandConfig(
448+
environment=environment,
445449
input_path=self.args.input_path,
446450
output_path=self.args.output_path,
447451
reload=self.args.reload,

strictdoc/cli/main.py

+32-34
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
1+
# pylint: disable=wrong-import-position
2+
# flake8: noqa: E402
3+
14
import os
25
import sys
36

4-
try:
5-
strictdoc_root_path = os.path.abspath(
6-
os.path.join(os.path.dirname(__file__), "..", "..")
7-
)
8-
if not os.path.isdir(strictdoc_root_path):
9-
raise FileNotFoundError
10-
sys.path.append(strictdoc_root_path)
11-
12-
from strictdoc import STRICTDOC_ROOT_PATH
13-
from strictdoc.cli.cli_arg_parser import (
14-
create_sdoc_args_parser,
15-
ExportCommandConfig,
16-
PassthroughCommandConfig,
17-
DumpGrammarCommandConfig,
18-
ImportExcelCommandConfig,
19-
ImportReqIFCommandConfig,
20-
)
21-
from strictdoc.commands.about_command import AboutCommand
22-
from strictdoc.commands.dump_grammar_command import DumpGrammarCommand
23-
from strictdoc.commands.version_command import VersionCommand
24-
from strictdoc.core.actions.export_action import ExportAction
25-
from strictdoc.core.actions.import_action import ImportAction
26-
from strictdoc.core.actions.passthrough_action import PassthroughAction
27-
from strictdoc.core.project_config import ProjectConfig, ProjectConfigLoader
28-
from strictdoc.helpers.parallelizer import Parallelizer
29-
from strictdoc.server.server import run_strictdoc_server
30-
31-
except FileNotFoundError:
32-
print("error: could not locate strictdoc's root folder.")
33-
sys.exit(1)
7+
strictdoc_root_path = os.path.abspath(
8+
os.path.join(os.path.dirname(__file__), "..", "..")
9+
)
10+
if not os.path.isdir(strictdoc_root_path):
11+
raise FileNotFoundError
12+
sys.path.append(strictdoc_root_path)
13+
14+
from strictdoc import environment
15+
from strictdoc.cli.cli_arg_parser import (
16+
create_sdoc_args_parser,
17+
ExportCommandConfig,
18+
PassthroughCommandConfig,
19+
DumpGrammarCommandConfig,
20+
ImportExcelCommandConfig,
21+
ImportReqIFCommandConfig,
22+
)
23+
from strictdoc.commands.about_command import AboutCommand
24+
from strictdoc.commands.dump_grammar_command import DumpGrammarCommand
25+
from strictdoc.commands.version_command import VersionCommand
26+
from strictdoc.core.actions.export_action import ExportAction
27+
from strictdoc.core.actions.import_action import ImportAction
28+
from strictdoc.core.actions.passthrough_action import PassthroughAction
29+
from strictdoc.core.project_config import ProjectConfig, ProjectConfigLoader
30+
from strictdoc.helpers.parallelizer import Parallelizer
31+
from strictdoc.server.server import run_strictdoc_server
3432

3533

3634
def _main(parallelizer):
@@ -57,7 +55,7 @@ def _main(parallelizer):
5755

5856
elif parser.is_export_command:
5957
config: ExportCommandConfig = parser.get_export_config(
60-
STRICTDOC_ROOT_PATH
58+
environment=environment
6159
)
6260
project_config: ProjectConfig = (
6361
ProjectConfigLoader.load_from_path_or_get_default(
@@ -74,7 +72,7 @@ def _main(parallelizer):
7472
export_action.export()
7573

7674
elif parser.is_server_command:
77-
server_config = parser.get_server_config()
75+
server_config = parser.get_server_config(environment=environment)
7876
project_config: ProjectConfig = (
7977
ProjectConfigLoader.load_from_path_or_get_default(
8078
path_to_config_dir=server_config.input_path
@@ -86,14 +84,14 @@ def _main(parallelizer):
8684

8785
elif parser.is_import_command_reqif:
8886
import_config: ImportReqIFCommandConfig = (
89-
parser.get_import_config_reqif(STRICTDOC_ROOT_PATH)
87+
parser.get_import_config_reqif(environment.path_to_strictdoc)
9088
)
9189
import_action = ImportAction()
9290
import_action.do_import(import_config)
9391

9492
elif parser.is_import_command_excel:
9593
import_config: ImportExcelCommandConfig = (
96-
parser.get_import_config_excel(STRICTDOC_ROOT_PATH)
94+
parser.get_import_config_excel(environment.path_to_strictdoc)
9795
)
9896
import_action = ImportAction()
9997
import_action.do_import(import_config)

strictdoc/core/environment.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os
2+
import sys
3+
4+
5+
class SDocRuntimeEnvironment:
6+
def __init__(self, path_to_init: str):
7+
assert isinstance(path_to_init, str), path_to_init
8+
9+
# FIXME: Delete me.
10+
self.path_to_init = path_to_init
11+
# https://stackoverflow.com/a/55556360/598057
12+
self.is_nuitka = "__compiled__" in globals()
13+
self.is_py_installer = (
14+
getattr(sys, "frozen", False) and not self.is_nuitka
15+
)
16+
17+
# "frozen" attribute is set to True by PyInstaller, and it looks like
18+
# Nuitka does the same.
19+
self.is_binary_dist: bool = (
20+
getattr(sys, "frozen", False) or self.is_nuitka
21+
)
22+
23+
if self.is_binary_dist:
24+
# When it is a binary distribution, we don't have a Python package
25+
# file structure but only a binary with surrounding libraries and
26+
# data files (Jinja HTML files, CSS, JS, ...).
27+
path_to_main = os.path.abspath(sys.argv[0])
28+
path_to_main_dir = os.path.dirname(os.path.abspath(path_to_main))
29+
30+
# Nuitka
31+
if self.is_nuitka:
32+
self.path_to_strictdoc = path_to_main_dir
33+
# PyInstaller
34+
else:
35+
self.path_to_strictdoc = (
36+
sys._MEIPASS # pylint: disable=protected-access, no-member
37+
)
38+
else:
39+
self.path_to_strictdoc = os.path.abspath(
40+
os.path.join(path_to_init, "..", "..")
41+
)
42+
if not os.path.isdir(self.path_to_strictdoc):
43+
raise FileNotFoundError(self.path_to_strictdoc)
44+
if not os.path.isabs(self.path_to_strictdoc):
45+
raise EnvironmentError(
46+
"Path to strictdoc's package path must be an absolute path: "
47+
f"{self.path_to_strictdoc}"
48+
)
49+
50+
def get_static_files_path(self):
51+
if self.is_binary_dist:
52+
return os.path.join(self.path_to_strictdoc, "_static")
53+
return os.path.join(
54+
self.path_to_strictdoc, "strictdoc/export/html/_static"
55+
)
56+
57+
def get_extra_static_files_path(self):
58+
if self.is_binary_dist:
59+
return os.path.join(self.path_to_strictdoc, "_static_extra")
60+
return os.path.join(
61+
self.path_to_strictdoc, "strictdoc/export/html/_static_extra"
62+
)
63+
64+
def get_path_to_rst_templates(self):
65+
if self.is_py_installer:
66+
# If the application is run as a bundle, the PyInstaller bootloader
67+
# extends the sys module by a flag frozen=True and sets the app
68+
# path into variable _MEIPASS'.
69+
bundle_dir = (
70+
sys._MEIPASS # pylint: disable=protected-access, no-member
71+
)
72+
return os.path.join(bundle_dir, "templates/rst")
73+
if self.is_nuitka:
74+
return os.path.join(self.path_to_strictdoc, "templates/rst")
75+
# Normal Python
76+
return os.path.join(
77+
self.path_to_strictdoc, "strictdoc", "export", "rst", "templates"
78+
)
79+
80+
def get_path_to_html_templates(self):
81+
if self.is_py_installer:
82+
# If the application is run as a bundle, the PyInstaller bootloader
83+
# extends the sys module by a flag frozen=True and sets the app
84+
# path into variable _MEIPASS'.
85+
bundle_dir = (
86+
sys._MEIPASS # pylint: disable=protected-access, no-member
87+
)
88+
return os.path.join(bundle_dir, "templates/html")
89+
if self.is_nuitka:
90+
return os.path.join(self.path_to_strictdoc, "templates/html")
91+
# Normal Python
92+
path_to_html_templates = os.path.join(
93+
self.path_to_strictdoc, "strictdoc", "export", "html", "templates"
94+
)
95+
assert os.path.isdir(path_to_html_templates), path_to_html_templates
96+
assert os.path.isabs(path_to_html_templates), path_to_html_templates
97+
return path_to_html_templates
98+
99+
def get_path_to_export_html(self):
100+
if self.is_nuitka:
101+
return self.path_to_strictdoc
102+
return os.path.join(
103+
self.path_to_strictdoc, "strictdoc", "export", "html"
104+
)

strictdoc/core/project_config.py

-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import pickle
32

43
import toml
54

@@ -10,13 +9,6 @@ class ProjectConfig:
109
def __init__(self, project_title: str):
1110
self.project_title: str = project_title
1211

13-
def dump_to_string(self) -> str:
14-
return pickle.dumps(self, 0).decode(encoding="utf8")
15-
16-
@staticmethod
17-
def config_from_string_dump(dump: str) -> "ProjectConfig":
18-
return pickle.loads(dump.encode(encoding="utf8"))
19-
2012
@staticmethod
2113
def default_config():
2214
return ProjectConfig(project_title=ProjectConfig.DEFAULT_PROJECT_TITLE)

0 commit comments

Comments
 (0)