Path:
strictdoc/features/source_file_view/view_object.py
Lines:
240
Non-empty lines:
204
Non-empty lines covered with requirements:
204 / 204 (100.0%)
Functions:
28
Functions covered by requirements:
28 / 28 (100.0%)
1
"""2
@relation(SDOC-SRS-36, scope=file)3
"""4
5
from dataclasses import dataclass
6
from typing import Any, List, Optional, Union
7
8
from markupsafe import Markup
9
10
from strictdoc import __version__
11
from strictdoc.backend.sdoc.constants import SDocMarkup
12
from strictdoc.backend.sdoc.models.anchor import Anchor
13
from strictdoc.backend.sdoc.models.document import SDocDocument
14
from strictdoc.backend.sdoc.models.document_view import NullViewElement
15
from strictdoc.backend.sdoc.models.model import SDocDocumentIF, SDocNodeIF
16
from strictdoc.backend.sdoc.models.node import SDocNode, SDocNodeField
17
from strictdoc.backend.sdoc_source_code.models.language_item_marker import (
18
LanguageItemMarker,
19
)20
from strictdoc.backend.sdoc_source_code.models.line_marker import LineMarker
21
from strictdoc.backend.sdoc_source_code.models.range_marker import (
22
ForwardRangeMarker,
23
RangeMarker,
24
)25
from strictdoc.backend.sdoc_source_code.models.source_file_info import (
26
SourceFileTraceabilityInfo,
27
)28
from strictdoc.core.document_tree import DocumentTree
29
from strictdoc.core.document_tree_iterator import DocumentTreeIterator
30
from strictdoc.core.file_system.source_tree import SourceFile
31
from strictdoc.core.project_config import ProjectConfig
32
from strictdoc.core.traceability_index import TraceabilityIndex
33
from strictdoc.export.html.document_type import DocumentType
34
from strictdoc.export.html.html_templates import JinjaEnvironment
35
from strictdoc.export.html.renderers.link_renderer import LinkRenderer
36
from strictdoc.export.html.renderers.markup_renderer import MarkupRenderer
37
from strictdoc.helpers.cast import assert_cast
38
39
40
@dataclass41
class SourceMarkerTuple:
42
ng_range_line_begin: int
43
ng_range_line_end: int
44
source_line: Markup
45
markers: List[
46
Union[LanguageItemMarker, ForwardRangeMarker, LineMarker, RangeMarker]
47
]48
49
def is_end(self) -> bool:
50
return any(map(lambda m_: m_.is_end(), self.markers))
51
52
def is_line_marker(self) -> bool:
53
return any(map(lambda m_: isinstance(m_, LineMarker), self.markers))
54
55
def is_range_marker(self) -> bool:
56
return any(map(lambda m_: m_.is_range_marker(), self.markers))
57
58
59
SourceLineEntry = Union[
60
Markup,
61
SourceMarkerTuple,
62
]63
64
65
@dataclass66
class SourceFileViewObject:
67
def __init__(
68
self,
69
*,
70
traceability_index: TraceabilityIndex,
71
trace_info: SourceFileTraceabilityInfo,
72
project_config: ProjectConfig,
73
link_renderer: LinkRenderer,
74
markup_renderer: MarkupRenderer,
75
text_renderer: MarkupRenderer,
76
source_file: SourceFile,
77
pygments_styles: Markup,
78
pygmented_source_file_lines: List[SourceLineEntry],
79
jinja_environment: JinjaEnvironment,
80
):81
self.traceability_index: TraceabilityIndex = traceability_index
82
self.trace_info: SourceFileTraceabilityInfo = trace_info
83
self.project_config: ProjectConfig = project_config
84
self.link_renderer: LinkRenderer = link_renderer
85
self.markup_renderer: MarkupRenderer = markup_renderer
86
self.text_renderer: MarkupRenderer = text_renderer
87
self.source_file: SourceFile = source_file
88
self.pygments_styles: Markup = pygments_styles
89
self.pygmented_source_file_lines: List[SourceLineEntry] = (
90
pygmented_source_file_lines91
)92
self.jinja_environment: JinjaEnvironment = jinja_environment
93
94
self.current_view = NullViewElement()
95
self.document_tree_iterator: DocumentTreeIterator = (
96
DocumentTreeIterator(
97
assert_cast(traceability_index.document_tree, DocumentTree)
98
)99
)100
self.is_running_on_server: bool = project_config.is_running_on_server
101
self.strictdoc_version = __version__
102
103
def get_document_level(self) -> int:
104
return self.source_file.level
105
106
def render_screen(self) -> Markup:
107
return self.jinja_environment.render_template_as_markup(
108
"features/source_file_view/index.jinja", view_object=self
109
)110
111
def render_detailed_node_for_banner(self, node_uid: str) -> Markup:
112
node: SDocNode = self.traceability_index.get_node_by_uid(node_uid)
113
return self.jinja_environment.render_template_as_markup(
114
"components/node_content/index_extends_readonly.jinja",
115
node=node,
116
view_object=self,
117
requirement_style="table",
118
)119
120
def render_node_title_for_banner_header(
121
self, marker: Union[Any], node_uid: str
122
) -> Markup:
123
node: SDocNode = self.traceability_index.get_node_by_uid(node_uid)
124
return self.jinja_environment.render_template_as_markup(
125
"features/source_file_view/node_title_for_banner_header.jinja",
126
node=node,
127
view_object=self,
128
role=marker.role,
129
is_forward_from_requirements_to_code=marker.is_forward(),
130
)131
132
def render_aside_requirement(
133
self,
134
node_uid: str,
135
range_begin: Optional[str] = None,
136
range_end: Optional[str] = None,
137
) -> Markup:
138
node: SDocNode = self.traceability_index.get_node_by_uid(node_uid)
139
return self.jinja_environment.render_template_as_markup(
140
"features/source_file_view/requirement.jinja",
141
requirement=node,
142
view_object=self,
143
requirement_style="table",
144
range_begin=range_begin,
145
range_end=range_end,
146
)147
148
def render_node_statement(self, node: SDocNode) -> Markup:
149
renderer = self.get_node_renderer(node)
150
return renderer.render_node_statement(DocumentType.DOCUMENT, node)
151
152
def render_node_rationale(self, node: SDocNode) -> Markup:
153
renderer = self.get_node_renderer(node)
154
return renderer.render_node_rationale(DocumentType.DOCUMENT, node)
155
156
def render_node_field(self, node_field: SDocNodeField) -> Markup:
157
assert isinstance(node_field, SDocNodeField), node_field
158
renderer = (
159
self.get_node_renderer(node_field.parent)
160
if node_field.parent is not None
161
else self.markup_renderer
162
)163
return renderer.render_node_field(DocumentType.DOCUMENT, node_field)
164
165
def render_url(self, url: str) -> str:
166
return self.link_renderer.render_url(url)
167
168
def render_marker_range_link(self, marker: RangeMarker) -> str:
169
return self.link_renderer.render_marker_range_link(
170
self.source_file.in_doctree_source_file_rel_path_posix,
171
self.source_file,
172
marker,
173
)174
175
def render_node_link(self, node: SDocNode) -> str:
176
assert isinstance(node, SDocNode), node
177
return self.link_renderer.render_node_link(
178
node, None, DocumentType.DOCUMENT
179
)180
181
def render_static_url(self, url: str) -> str:
182
return self.link_renderer.render_static_url(url)
183
184
def render_local_anchor(
185
self, node: Union[Anchor, SDocNode, SDocDocument]
186
) -> str:
187
return self.link_renderer.render_local_anchor(node)
188
189
def render_issues(
190
self,
191
node: Union[SDocNodeIF, SDocDocumentIF], # noqa: ARG002
192
field: Optional[str] = None, # noqa: ARG002
193
) -> str:
194
"""
195
FIXME: It is not great that this method is called from here.196
"""197
return ""
198
199
def get_node_renderer(self, node: SDocNode) -> MarkupRenderer:
200
parent_doc = node.get_parent_or_including_document()
201
parent_doc_config = parent_doc.config
202
if parent_doc_config.markup is SDocMarkup.RST:
203
return self.markup_renderer
204
return self.text_renderer
205
206
def get_source_file_path(self) -> str:
207
return self.source_file.in_doctree_source_file_rel_path_posix
208
209
def get_file_stats_lines_total(self) -> str:
210
return str(self.trace_info.file_stats.lines_total)
211
212
def get_file_stats_lines_total_non_empty(self) -> str:
213
return str(self.trace_info.file_stats.lines_non_empty)
214
215
def get_file_stats_non_empty_lines_covered(self) -> str:
216
covered = self.trace_info.ng_lines_covered
217
total_non_empty = self.trace_info.file_stats.lines_non_empty
218
percentage = (
219
(covered / total_non_empty * 100) if total_non_empty > 0 else 0
220
)221
return f"{covered} / {total_non_empty} ({percentage:.1f}%)"
222
223
def get_file_stats_functions_total(self) -> str:
224
return str(len(self.trace_info.functions))
225
226
def get_file_stats_functions_covered(self) -> str:
227
covered = self.trace_info.covered_functions
228
total = len(self.trace_info.functions)
229
percentage = (covered / total * 100) if total > 0 else 0
230
return f"{covered} / {total} ({percentage:.1f}%)"
231
232
def get_html2pdf_classes(self, node: SDocNode) -> str: # noqa: ARG002
233
"""
234
This view object class is coupled with the HTML2PDF-related template235
logic.236
237
The Source File View is not printed with HTML2PDF, so we keep its classes238
to noop.239
"""240
return ""