Path:
strictdoc/server/routers/other_router.py
Lines:
304
Non-empty lines:
273
Non-empty lines covered with requirements:
273 / 273 (100.0%)
Functions:
4
Functions covered by requirements:
4 / 4 (100.0%)
1
"""2
@relation(SDOC-SRS-111, scope=file)3
"""4
5
import os
6
import urllib
7
from contextlib import ExitStack
8
from copy import deepcopy
9
from datetime import datetime
10
from typing import Optional
11
12
from fastapi import APIRouter
13
from starlette.responses import HTMLResponse, Response
14
15
from strictdoc import __version__
16
from strictdoc.core.project_config import ProjectConfig
17
from strictdoc.export.html.document_type import DocumentType
18
from strictdoc.export.html.html_templates import HTMLTemplates
19
from strictdoc.export.html.renderers.link_renderer import LinkRenderer
20
from strictdoc.features.diff_and_changelog.change_container import (
21
ChangeContainer,
22
)23
from strictdoc.features.diff_and_changelog.change_generator import (
24
ChangeGenerator,
25
)26
from strictdoc.features.diff_and_changelog.diff_screen_results_view_object import (
27
DiffScreenResultsViewObject,
28
)29
from strictdoc.features.diff_and_changelog.diff_screen_view_object import (
30
DiffScreenViewObject,
31
)32
from strictdoc.features.diff_and_changelog.git_client import GitClient
33
from strictdoc.server.helpers.hierarchical_rw_lock_manager import (
34
HierarchicalRWLockManager,
35
)36
from strictdoc.server.routers.main_router import (
37
HTTP_STATUS_BAD_REQUEST,
38
HTTP_STATUS_PRECONDITION_FAILED,
39
)40
41
42
def create_other_router(
43
project_config: ProjectConfig,
44
*,
45
lock_manager: HierarchicalRWLockManager,
46
) -> APIRouter:
47
router = APIRouter()
48
49
html_templates = HTMLTemplates.create(
50
project_config=project_config,
51
enable_caching=False,
52
strictdoc_last_update=datetime.today(),
53
)54
55
@router.get("/diff")
56
def get_git_diff(
57
left_revision: Optional[str] = None,
58
right_revision: Optional[str] = None,
59
tab: Optional[str] = None,
60
) -> Response:
61
if not project_config.is_activated_diff():
62
return Response(
63
content="The DIFF feature is not activated in the project config.",
64
status_code=HTTP_STATUS_PRECONDITION_FAILED,
65
)66
if tab is not None:
67
if tab not in ("diff", "changelog"):
68
return Response(
69
content="The tab= parameter must be either 'diff' or 'changelog'.",
70
status_code=HTTP_STATUS_BAD_REQUEST,
71
)72
else:
73
tab = "diff"
74
75
error_message: Optional[str] = None
76
77
if (
78
left_revision is not None
79
and len(left_revision) > 0
80
and right_revision is not None
81
and len(right_revision) > 0
82
):83
git_client = GitClient(".")
84
try:
85
if left_revision != "HEAD+":
86
git_client.check_revision(left_revision)
87
else:
88
raise LookupError(
89
"Left revision argument 'HEAD+' is not supported. "90
"'HEAD+' can only be used as a right revision argument."91
)92
93
if right_revision != "HEAD+":
94
git_client.check_revision(right_revision)
95
except LookupError as exception_:
96
error_message = exception_.args[0]
97
elif (left_revision is not None and len(left_revision) > 0) or (
98
right_revision is not None and len(right_revision) > 0
99
):100
error_message = "Valid Git revisions must be provided."
101
else:
102
# In the case when both revisions are empty, we load the starting103
# diff page.104
pass105
106
view_object = DiffScreenViewObject(
107
project_config=project_config,
108
results=False,
109
left_revision=left_revision,
110
right_revision=right_revision,
111
error_message=error_message,
112
tab=tab,
113
)114
output = view_object.render_screen(html_templates.jinja_environment())
115
status_code = 200 if error_message is None else 422
116
return HTMLResponse(content=output, status_code=status_code)
117
118
@router.get("/diff_result")
119
def get_git_diff_result(
120
left_revision: Optional[str] = None,
121
right_revision: Optional[str] = None,
122
tab: Optional[str] = None,
123
) -> Response:
124
if not project_config.is_activated_diff():
125
return Response(
126
content="The DIFF feature is not activated in the project config.",
127
status_code=HTTP_STATUS_PRECONDITION_FAILED,
128
)129
if tab is not None and tab not in ("diff", "changelog"):
130
return Response(
131
content="The tab= parameter must be either 'diff' or 'changelog'.",
132
status_code=HTTP_STATUS_PRECONDITION_FAILED,
133
)134
elif tab is None:
135
tab = "diff"
136
137
left_revision_resolved = None
138
right_revision_resolved = None
139
140
results = False
141
error_message: Optional[str] = None
142
143
if (
144
left_revision is not None
145
and len(left_revision) > 0
146
and right_revision is not None
147
and len(right_revision) > 0
148
):149
git_client = GitClient(".")
150
try:
151
if left_revision != "HEAD+":
152
left_revision_resolved = git_client.check_revision(
153
left_revision154
)155
else:
156
raise LookupError(
157
"Left revision argument 'HEAD+' is not supported. "158
"'HEAD+' can only be used as a right revision argument."159
)160
161
if right_revision == "HEAD+":
162
right_revision_resolved = "HEAD+"
163
else:
164
right_revision_resolved = git_client.check_revision(
165
right_revision166
)167
168
results = True
169
except LookupError as exception_:
170
error_message = exception_.args[0]
171
elif (left_revision is not None and len(left_revision) > 0) or (
172
right_revision is not None and len(right_revision) > 0
173
):174
error_message = "Valid Git revisions must be provided."
175
else:
176
# In the case when both revisions are empty, we load the starting177
# diff page.178
pass179
180
path_to_template = (
181
"features/diff_and_changelog/frame_changelog_result.jinja"182
if tab == "changelog"
183
else "features/diff_and_changelog/frame_diff_result.jinja"
184
)185
template = html_templates.jinja_environment().get_template(
186
path_to_template187
)188
189
link_renderer = LinkRenderer(
190
root_path="", static_path=project_config.dir_for_sdoc_assets
191
)192
193
left_revision_urlencoded = (
194
urllib.parse.quote(left_revision)
195
if left_revision is not None
196
else ""
197
)198
right_revision_urlencoded = (
199
urllib.parse.quote(right_revision)
200
if right_revision is not None
201
else ""
202
)203
204
if not results:
205
output = template.render(
206
project_config=project_config,
207
document_type=DocumentType.DOCUMENT.value,
208
link_document_type=DocumentType.DOCUMENT.value,
209
strictdoc_version=__version__,
210
link_renderer=link_renderer,
211
results=False,
212
left_revision=left_revision,
213
left_revision_urlencoded=left_revision_urlencoded,
214
right_revision=right_revision,
215
right_revision_urlencoded=right_revision_urlencoded,
216
error_message=error_message,
217
)218
status_code = 200 if error_message is None else 422
219
return HTMLResponse(content=output, status_code=status_code)
220
221
assert left_revision_resolved is not None
222
assert right_revision_resolved is not None
223
224
def open_git_client_for_revision(revision: str) -> GitClient:
225
if revision == "HEAD+":
226
# Serialize HEAD+ snapshot creation with main router writes.227
with lock_manager.acquire_global_write():
228
return exit_stack.enter_context(
229
GitClient.create_cached_repo_from_local_copy(
230
revision, project_config
231
)232
)233
return exit_stack.enter_context(
234
GitClient.create_cached_repo_from_local_copy(
235
revision, project_config
236
)237
)238
239
with ExitStack() as exit_stack:
240
git_client_lhs = open_git_client_for_revision(
241
left_revision_resolved242
)243
git_client_rhs = open_git_client_for_revision(
244
right_revision_resolved245
)246
247
project_config_copy_lhs: ProjectConfig = deepcopy(project_config)
248
assert project_config_copy_lhs.input_paths is not None
249
project_config_copy_rhs: ProjectConfig = deepcopy(project_config)
250
assert project_config_copy_rhs.input_paths is not None
251
252
export_input_rel_path = os.path.relpath(
253
project_config_copy_lhs.input_paths[0], os.getcwd()
254
)255
export_input_abs_path = os.path.join(
256
git_client_lhs.path_to_git_root, export_input_rel_path
257
)258
project_config_copy_lhs.input_paths = [export_input_abs_path]
259
260
export_input_rel_path = os.path.relpath(
261
project_config_copy_rhs.input_paths[0], os.getcwd()
262
)263
export_input_abs_path = os.path.join(
264
git_client_rhs.path_to_git_root, export_input_rel_path
265
)266
project_config_copy_rhs.input_paths = [export_input_abs_path]
267
268
change_container: ChangeContainer = ChangeGenerator.generate(
269
lhs_project_config=project_config_copy_lhs,
270
rhs_project_config=project_config_copy_rhs,
271
)272
273
assert (
274
change_container.traceability_index_lhs.document_tree
275
is not None
276
)277
assert (
278
change_container.traceability_index_rhs.document_tree
279
is not None
280
)281
view_object = DiffScreenResultsViewObject(
282
project_config=project_config,
283
change_container=change_container,
284
document_tree_lhs=change_container.traceability_index_lhs.document_tree,
285
document_tree_rhs=change_container.traceability_index_rhs.document_tree,
286
documents_iterator_lhs=change_container.documents_iterator_lhs,
287
documents_iterator_rhs=change_container.documents_iterator_rhs,
288
left_revision=left_revision,
289
right_revision=right_revision,
290
lhs_stats=change_container.lhs_stats,
291
rhs_stats=change_container.rhs_stats,
292
change_stats=change_container.change_stats,
293
traceability_index_lhs=change_container.traceability_index_lhs,
294
traceability_index_rhs=change_container.traceability_index_rhs,
295
tab=tab,
296
)297
output = template.render(view_object=view_object)
298
299
return HTMLResponse(
300
content=output,
301
status_code=200,
302
)303
304
return router