StrictDoc Documentation
strictdoc/export/html/form_objects/grammar_form_object.py
Source file coverage
Path:
strictdoc/export/html/form_objects/grammar_form_object.py
Lines:
240
Non-empty lines:
209
Non-empty lines covered with requirements:
209 / 209 (100.0%)
Functions:
15
Functions covered by requirements:
15 / 15 (100.0%)
1
"""
2
@relation(SDOC-SRS-56, scope=file)
3
"""
4
 
5
from typing import List, Optional, Set
6
 
7
from markupsafe import Markup
8
from starlette.datastructures import FormData
9
 
10
from strictdoc.backend.sdoc.models.document import SDocDocument
11
from strictdoc.backend.sdoc.models.document_grammar import (
12
    DocumentGrammar,
13
)
14
from strictdoc.backend.sdoc.models.grammar_element import GrammarElement
15
from strictdoc.core.project_config import ProjectConfig
16
from strictdoc.export.html.form_objects.rows.row_with_grammar_element_form_object import (
17
    RowWithGrammarElementFormObject,
18
)
19
from strictdoc.export.html.html_templates import JinjaEnvironment
20
from strictdoc.helpers.auto_described import auto_described
21
from strictdoc.helpers.cast import assert_cast
22
from strictdoc.helpers.form_data import (
23
    ParsedFormData,
24
    parse_form_data,
25
)
26
from strictdoc.helpers.mid import MID
27
from strictdoc.helpers.string import is_uppercase_underscore_string
28
from strictdoc.server.error_object import ErrorObject
29
from strictdoc.server.helpers.turbo import render_turbo_stream
30
 
31
 
32
@auto_described
33
class GrammarElementFormField:
34
    def __init__(
35
        self,
36
        is_new: bool,
37
        field_mid: str,
38
        field_name: str,
39
        document_mid: str,
40
    ):
41
        self.is_new: bool = is_new
42
        self.field_mid: str = field_mid
43
        self.field_name: str = field_name
44
        self.document_mid: str = document_mid
45
 
46
    @staticmethod
47
    def create(
48
        *,
49
        is_new: bool,
50
        grammar_element: GrammarElement,
51
        document_mid: str,
52
    ) -> "GrammarElementFormField":
53
        return GrammarElementFormField(
54
            is_new=is_new,
55
            field_mid=grammar_element.mid,
56
            field_name=grammar_element.tag,
57
            document_mid=document_mid,
58
        )
59
 
60
    def get_input_field_name(self) -> str:
61
        return f"document_grammar_element_field[{self.field_mid}][field_name]"
62
 
63
    def get_input_field_is_new(self) -> str:
64
        return f"document_grammar_element_field[{self.field_mid}][is_new]"
65
 
66
    def get_is_new_as_string(self) -> str:
67
        return "true" if self.is_new else "false"
68
 
69
 
70
@auto_described
71
class GrammarFormObject(ErrorObject):
72
    def __init__(
73
        self,
74
        *,
75
        document_mid: str,
76
        fields: List[GrammarElementFormField],
77
        project_config: ProjectConfig,
78
        jinja_environment: JinjaEnvironment,
79
        imported_grammar_file: Optional[str],
80
    ):
81
        assert isinstance(document_mid, str), document_mid
82
        super().__init__()
83
        self.document_mid = document_mid
84
        self.fields: List[GrammarElementFormField] = fields
85
        self.project_config: ProjectConfig = project_config
86
        self.jinja_environment: JinjaEnvironment = jinja_environment
87
        self.imported_grammar_file: Optional[str] = imported_grammar_file
88
 
89
    @staticmethod
90
    def create_from_request(
91
        *,
92
        document_mid: str,
93
        request_form_data: FormData,
94
        project_config: ProjectConfig,
95
        jinja_environment: JinjaEnvironment,
96
    ) -> "GrammarFormObject":
97
        form_object_fields: List[GrammarElementFormField] = []
98
        request_form_data_as_list = [
99
            (field_name, field_value)
100
            for field_name, field_value in request_form_data.multi_items()
101
        ]
102
        request_form_dict: ParsedFormData = assert_cast(
103
            parse_form_data(request_form_data_as_list), dict
104
        )
105
 
106
        document_grammar_fields = request_form_dict[
107
            "document_grammar_element_field"
108
        ]
109
 
110
        if not isinstance(document_grammar_fields, dict):
111
            raise TypeError(
112
                f"Expected a dict, but got {type(document_grammar_fields)}"
113
            )
114
 
115
        for field_mid, field_dict in document_grammar_fields.items():
116
            assert isinstance(field_dict, dict), type(field_dict)
117
 
118
            is_new = field_dict["is_new"] == "true"
119
            field_name = assert_cast(field_dict["field_name"], str)
120
 
121
            form_object_field = GrammarElementFormField(
122
                is_new=is_new,
123
                field_mid=field_mid,
124
                field_name=field_name,
125
                document_mid=document_mid,
126
            )
127
            form_object_fields.append(form_object_field)
128
 
129
        form_object = GrammarFormObject(
130
            document_mid=document_mid,
131
            fields=form_object_fields,
132
            project_config=project_config,
133
            jinja_environment=jinja_environment,
134
            imported_grammar_file=None,
135
        )
136
        return form_object
137
 
138
    @staticmethod
139
    def create_from_document(
140
        *,
141
        document: SDocDocument,
142
        project_config: ProjectConfig,
143
        jinja_environment: JinjaEnvironment,
144
    ) -> "GrammarFormObject":
145
        assert isinstance(document, SDocDocument)
146
        assert isinstance(document.grammar, DocumentGrammar)
147
 
148
        grammar: DocumentGrammar = document.grammar
149
 
150
        grammar_element_form_fields: List[GrammarElementFormField] = []
151
 
152
        for element_ in grammar.elements:
153
            grammar_form_field = GrammarElementFormField.create(
154
                is_new=False,
155
                grammar_element=element_,
156
                document_mid=document.reserved_mid,
157
            )
158
            grammar_element_form_fields.append(grammar_form_field)
159
 
160
        return GrammarFormObject(
161
            document_mid=document.reserved_mid,
162
            fields=grammar_element_form_fields,
163
            project_config=project_config,
164
            jinja_environment=jinja_environment,
165
            imported_grammar_file=grammar.import_from_file,
166
        )
167
 
168
    def validate(self) -> bool:
169
        fields_so_far: Set[str] = set()
170
        for field in self.fields:
171
            if len(field.field_name) == 0:
172
                self.add_error(
173
                    field.get_input_field_name(),
174
                    "Provide a name for the grammar element.",
175
                )
176
                continue
177
 
178
            if not is_uppercase_underscore_string(field.field_name):
179
                self.add_error(
180
                    field.get_input_field_name(),
181
                    (
182
                        "Grammar element title shall consist of "
183
                        "uppercase letters, digits and single underscores."
184
                    ),
185
                )
186
 
187
            if field.field_name in fields_so_far:
188
                self.add_error(
189
                    field.get_input_field_name(),
190
                    f"Grammar element {field.field_name} is not unique.",
191
                )
192
            else:
193
                fields_so_far.add(field.field_name)
194
 
195
        return len(self.errors) == 0
196
 
197
    def render(self) -> Markup:
198
        rendered_template = self.jinja_environment.render_template_as_markup(
199
            "components/grammar_form/index.jinja", form_object=self
200
        )
201
        return render_turbo_stream(
202
            content=rendered_template, action="update", target="modal"
203
        )
204
 
205
    def render_row_with_grammar_element(
206
        self, field: GrammarElementFormField
207
    ) -> Markup:
208
        form_object = RowWithGrammarElementFormObject(
209
            field=field,
210
            errors=self.errors,
211
            jinja_environment=self.jinja_environment,
212
        )
213
        return form_object.render()
214
 
215
    def render_row_with_new_grammar_element(self) -> str:
216
        field: GrammarElementFormField = GrammarElementFormField(
217
            is_new=True,
218
            field_mid=MID.create(),
219
            field_name="",
220
            document_mid=self.document_mid,
221
        )
222
        form_object = RowWithGrammarElementFormObject(
223
            field=field,
224
            errors=self.errors,
225
            jinja_environment=self.jinja_environment,
226
        )
227
        rendered_template: str = form_object.render()
228
        return render_turbo_stream(
229
            content=rendered_template,
230
            action="append",
231
            target="document__editable_grammar_elements",
232
        )
233
 
234
    @staticmethod
235
    def render_close_form() -> str:
236
        return render_turbo_stream(
237
            content="",
238
            action="update",
239
            target="modal",
240
        )