StrictDoc Documentation
strictdoc/features/source_file_view/view_object.py
Source file coverage
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
@dataclass
41
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
@dataclass
66
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_lines
91
        )
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 template
235
        logic.
236
 
237
        The Source File View is not printed with HTML2PDF, so we keep its classes
238
        to noop.
239
        """
240
        return ""