StrictDoc Documentation
strictdoc/export/rst/writer.py
Source file coverage
Path:
strictdoc/export/rst/writer.py
Lines:
145
Non-empty lines:
128
Non-empty lines covered with requirements:
128 / 128 (100.0%)
Functions:
8
Functions covered by requirements:
8 / 8 (100.0%)
1
from enum import Enum
2
from typing import Optional
3
 
4
from strictdoc.backend.sdoc.models.anchor import Anchor
5
from strictdoc.backend.sdoc.models.document import SDocDocument
6
from strictdoc.backend.sdoc.models.inline_link import InlineLink
7
from strictdoc.backend.sdoc.models.node import SDocNode, SDocNodeField
8
from strictdoc.core.document_iterator import SDocDocumentIterator
9
from strictdoc.core.traceability_index import TraceabilityIndex
10
from strictdoc.export.rst.rst_templates import RSTTemplates
11
from strictdoc.helpers.rst import escape_str_after_inline_markup
12
 
13
 
14
class TAG(Enum):
15
    SECTION = 1
16
    REQUIREMENT = 2
17
    COMPOSITE_REQUIREMENT = 3
18
 
19
 
20
class RSTWriter:
21
    def __init__(self, index: TraceabilityIndex):
22
        self.index = index
23
 
24
    def write(self, document: SDocDocument, single_document: bool) -> str:
25
        document_iterator = SDocDocumentIterator(document)
26
        output = ""
27
 
28
        if not single_document:
29
            document_uid: Optional[str] = None
30
            if document.config.uid is not None:
31
                document_uid = document.config.uid
32
            output += self._print_rst_header(
33
                document.title, 0, reference_uid=document_uid
34
            )
35
 
36
        for content_node, node_context_ in document_iterator.all_content():
37
            if (
38
                isinstance(content_node, SDocNode)
39
                and content_node.node_type == "SECTION"
40
            ):
41
                assert node_context_.get_level() is not None
42
                assert content_node.reserved_title is not None
43
                output += self._print_rst_header(
44
                    content_node.reserved_title,
45
                    node_context_.get_level(),
46
                    content_node.reserved_uid,
47
                )
48
 
49
            elif isinstance(content_node, SDocNode):
50
                output += self._print_requirement_fields(content_node)
51
 
52
        if output.endswith("\n\n"):
53
            output = output[:-1]
54
        return output.lstrip()
55
 
56
    @staticmethod
57
    def _print_rst_header_2(string: str, level: int) -> str:
58
        assert isinstance(string, str), string
59
        assert isinstance(level, int), level
60
        chars = {
61
            0: "$",
62
            1: "=",
63
            2: "-",
64
            3: "~",
65
            4: "^",
66
            5: '"',
67
            6: "#",
68
            7: "'",
69
        }
70
        header_char = chars[level]
71
        output = ""
72
        output += string
73
        output += "\n"
74
        output += header_char.rjust(len(string), header_char)
75
        return output
76
 
77
    @staticmethod
78
    def _print_rst_header(
79
        string: str, level: int, reference_uid: Optional[str]
80
    ) -> str:
81
        assert isinstance(string, str), string
82
        assert isinstance(level, int), level
83
        chars = {
84
            0: "$",
85
            1: "=",
86
            2: "-",
87
            3: "~",
88
            4: "^",
89
            5: '"',
90
            6: "#",
91
            7: "'",
92
        }
93
        header_char = chars[level]
94
        output = ""
95
 
96
        # An RST reference looks like this:
97
        # .. _SDOC-HIGH-VALIDATION:
98
        if reference_uid is not None:
99
            assert len(reference_uid) > 0, reference_uid
100
            output += f".. _{reference_uid}:\n\n"
101
 
102
        output += string
103
        output += "\n"
104
        output += header_char.rjust(len(string), header_char)
105
        output += "\n\n"
106
        return output
107
 
108
    def _print_requirement_fields(self, section_content: SDocNode) -> str:
109
        requirement_template = RSTTemplates.jinja_environment.get_template(
110
            "requirement.jinja.rst"
111
        )
112
        output = requirement_template.render(
113
            requirement=section_content,
114
            index=self.index,
115
            _print_rst_header=self._print_rst_header_2,
116
            _print_node_field=self._print_node_field,
117
        )
118
        return output
119
 
120
    def _print_node_field(self, object_with_parts: SDocNodeField) -> str:
121
        output = ""
122
        prev_part = None
123
        for part in object_with_parts.parts:
124
            if isinstance(part, str):
125
                if isinstance(prev_part, InlineLink):
126
                    output += escape_str_after_inline_markup(part)
127
                else:
128
                    output += part
129
            elif isinstance(part, InlineLink):
130
                node_or_none = self.index.get_linkable_node_by_uid(part.link)
131
                # Labels that aren't placed before a section title can still be
132
                # referenced, but you must give the link an explicit title,
133
                # using this syntax: :ref:`Link title <label-name>`.
134
                # https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html
135
                node_display_title = node_or_none.get_display_title(
136
                    include_toc_number=False
137
                )
138
                output += f":ref:`{node_display_title} <{part.link}>`"
139
            elif isinstance(part, Anchor):
140
                output += f".. _{part.value}:\n"
141
            else:
142
                raise NotImplementedError
143
            prev_part = part
144
 
145
        return output