Path:
strictdoc/server/routers/other_router.py
Lines:
321
Non-empty lines:
289
Non-empty lines covered with requirements:
289 / 289 (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
left_revision_tags = git_client.get_tags_for_revision(
225
left_revision_resolved226
)227
right_revision_tags = (
228
git_client.get_tags_for_revision(right_revision_resolved)
229
if right_revision_resolved != "HEAD+"
230
else []
231
)232
233
def open_git_client_for_revision(revision: str) -> GitClient:
234
if revision == "HEAD+":
235
# Serialize HEAD+ snapshot creation with main router writes.236
with lock_manager.acquire_global_write():
237
return exit_stack.enter_context(
238
GitClient.create_cached_repo_from_local_copy(
239
revision, project_config
240
)241
)242
return exit_stack.enter_context(
243
GitClient.create_cached_repo_from_local_copy(
244
revision, project_config
245
)246
)247
248
with ExitStack() as exit_stack:
249
git_client_lhs = open_git_client_for_revision(
250
left_revision_resolved251
)252
git_client_rhs = open_git_client_for_revision(
253
right_revision_resolved254
)255
256
project_config_copy_lhs: ProjectConfig = deepcopy(project_config)
257
assert project_config_copy_lhs.input_paths is not None
258
project_config_copy_rhs: ProjectConfig = deepcopy(project_config)
259
assert project_config_copy_rhs.input_paths is not None
260
261
export_input_rel_path = os.path.relpath(
262
project_config_copy_lhs.input_paths[0], os.getcwd()
263
)264
export_input_abs_path = os.path.join(
265
git_client_lhs.path_to_git_root, export_input_rel_path
266
)267
project_config_copy_lhs.input_paths = [export_input_abs_path]
268
269
export_input_rel_path = os.path.relpath(
270
project_config_copy_rhs.input_paths[0], os.getcwd()
271
)272
export_input_abs_path = os.path.join(
273
git_client_rhs.path_to_git_root, export_input_rel_path
274
)275
project_config_copy_rhs.input_paths = [export_input_abs_path]
276
277
change_container: ChangeContainer = ChangeGenerator.generate(
278
lhs_project_config=project_config_copy_lhs,
279
rhs_project_config=project_config_copy_rhs,
280
)281
282
assert (
283
change_container.traceability_index_lhs.document_tree
284
is not None
285
)286
assert (
287
change_container.traceability_index_rhs.document_tree
288
is not None
289
)290
# Unlike change_generator.py's static export, left_revision/291
# right_revision here must stay the raw expressions the user292
# submitted (e.g. "HEAD^"): the form/turbo-frame/nav-tab links293
# resubmit these exact values back to this route, so replacing294
# them with a "<expression> (<short-hash>)" display string would295
# break re-diffing from the results screen.296
view_object = DiffScreenResultsViewObject(
297
project_config=project_config,
298
change_container=change_container,
299
document_tree_lhs=change_container.traceability_index_lhs.document_tree,
300
document_tree_rhs=change_container.traceability_index_rhs.document_tree,
301
documents_iterator_lhs=change_container.documents_iterator_lhs,
302
documents_iterator_rhs=change_container.documents_iterator_rhs,
303
left_revision=left_revision,
304
right_revision=right_revision,
305
left_revision_tags=left_revision_tags,
306
right_revision_tags=right_revision_tags,
307
lhs_stats=change_container.lhs_stats,
308
rhs_stats=change_container.rhs_stats,
309
change_stats=change_container.change_stats,
310
traceability_index_lhs=change_container.traceability_index_lhs,
311
traceability_index_rhs=change_container.traceability_index_rhs,
312
tab=tab,
313
)314
output = template.render(view_object=view_object)
315
316
return HTMLResponse(
317
content=output,
318
status_code=200,
319
)320
321
return router