Path:
strictdoc/backend/sdoc/processor.py
Lines:
223
Non-empty lines:
194
Non-empty lines covered with requirements:
194 / 194 (100.0%)
Functions:
14
Functions covered by requirements:
14 / 14 (100.0%)
1
import os.path
2
import re
3
from typing import Any, Callable, Dict, List, Optional, Union, cast
4
5
from textx import TextXSyntaxError, get_model
6
7
from strictdoc.backend.sdoc.document_reference import DocumentReference
8
from strictdoc.backend.sdoc.models.document import SDocDocument
9
from strictdoc.backend.sdoc.models.document_config import DocumentConfig
10
from strictdoc.backend.sdoc.models.document_from_file import DocumentFromFile
11
from strictdoc.backend.sdoc.models.document_grammar import DocumentGrammar
12
from strictdoc.backend.sdoc.models.document_view import DocumentView
13
from strictdoc.backend.sdoc.models.grammar_element import GrammarElement
14
from strictdoc.backend.sdoc.models.model import (
15
SDocDocumentFromFileIF,
16
SDocDocumentIF,
17
SDocNodeIF,
18
)19
from strictdoc.backend.sdoc.models.node import (
20
SDocNode,
21
SDocNodeField,
22
)23
from strictdoc.helpers.exception import StrictDocException
24
from strictdoc.helpers.textx import (
25
SupportsTxPosition,
26
preserve_source_location_data,
27
)28
29
30
class ParseContext:
31
def __init__(
32
self, path_to_sdoc_file: Optional[str], migrate_sections: bool = False
33
):34
self.path_to_sdoc_file: Optional[str] = path_to_sdoc_file
35
self.path_to_sdoc_dir: Optional[str] = None
36
if path_to_sdoc_file is not None:
37
self.path_to_sdoc_dir = os.path.dirname(path_to_sdoc_file)
38
self.document_grammar: Optional[DocumentGrammar] = None
39
self.document_reference: DocumentReference = DocumentReference()
40
self.context_document_reference: DocumentReference = DocumentReference()
41
self.document_config: Optional[DocumentConfig] = None
42
self.document_view: Optional[DocumentView] = None
43
self.document_has_requirements = False
44
45
self.fragments_from_files: List[SDocDocumentFromFileIF] = []
46
self.migrate_sections: bool = migrate_sections
47
48
49
class SDocParsingProcessor:
50
def __init__(self, parse_context: ParseContext) -> None:
51
self.parse_context: ParseContext = parse_context
52
53
def process_document(self, document: SDocDocument) -> None:
54
document.grammar = (
55
self.parse_context.document_grammar
56
or DocumentGrammar.create_default(
57
document,
58
enable_mid=document.config.enable_mid or False,
59
)60
)61
document.ng_including_document_reference = (
62
self.parse_context.context_document_reference
63
)64
65
def get_default_processors(self) -> Dict[str, Callable[..., None]]:
66
return {
67
"SDocDocument": self.process_document,
68
"DocumentConfig": self.process_document_config,
69
"DocumentGrammar": self.process_document_grammar,
70
"GrammarElement": self.process_document_grammar_element,
71
"DocumentView": self.process_document_view,
72
"SDocSection": self.process_section,
73
"DocumentFromFile": self.process_document_from_file,
74
"SDocCompositeNode": self.process_requirement,
75
"SDocNode": self.process_requirement,
76
"SDocNodeField": self.process_node_field,
77
}78
79
def process_document_config(self, document_config: DocumentConfig) -> None:
80
the_model = get_model(document_config)
81
line_start, col_start = the_model._tx_parser.pos_to_linecol(
82
cast(SupportsTxPosition, document_config)._tx_position
83
)84
document_config.ng_line_start = line_start
85
document_config.ng_col_start = col_start
86
self.parse_context.document_config = document_config
87
88
def process_document_grammar(
89
self, document_grammar: DocumentGrammar
90
) -> None:
91
assert self.parse_context.document_config is not None
92
93
if (import_from_file_ := document_grammar.import_from_file) is not None:
94
if re.search(r"\.\.|[/\\]", import_from_file_):
95
raise StrictDocException(
96
"[GRAMMAR]: "97
"IMPORT_FROM_FILE must not contain any '..', '/', '\\' characters: "
98
f"{import_from_file_}."
99
)100
101
preserve_source_location_data(document_grammar)
102
# FIXME: It would be great to move forward and remove this.103
if not document_grammar.has_text_element():
104
document_grammar.add_element_first(
105
DocumentGrammar.create_default_text_element(
106
document_grammar,
107
enable_mid=self.parse_context.document_config.enable_mid
108
is True,
109
)110
)111
self.parse_context.document_grammar = document_grammar
112
113
def process_document_grammar_element(
114
self, grammar_element: GrammarElement
115
) -> None:
116
preserve_source_location_data(grammar_element)
117
118
def process_document_view(self, document_view: DocumentView) -> None:
119
self.parse_context.document_view = document_view
120
121
the_model = get_model(document_view)
122
line_start, col_start = the_model._tx_parser.pos_to_linecol(
123
cast(SupportsTxPosition, document_view)._tx_position
124
)125
document_view.ng_line_start = line_start
126
document_view.ng_col_start = col_start
127
128
def process_section(self, _: Any) -> None:
129
raise StrictDocException("""
130
[SECTION] elements are no longer supported by StrictDoc. See the migration guide for more details:\n"
131
"https://strictdoc.readthedocs.io/en/latest/latest/docs/strictdoc_01_user_guide.html#SECTION-UG-NODE-MIGRATION."132
""")
133
134
def process_document_from_file(
135
self, document_from_file: DocumentFromFile
136
) -> None:
137
assert isinstance(document_from_file, DocumentFromFile), (
138
document_from_file139
)140
141
# Windows paths are backslashes, so using abspath in addition.142
assert self.parse_context.path_to_sdoc_dir is not None
143
resolved_path_to_fragment_file = os.path.abspath(
144
os.path.join(
145
self.parse_context.path_to_sdoc_dir, document_from_file.file
146
)147
)148
if not resolved_path_to_fragment_file.endswith(".sdoc"):
149
raise StrictDocException(
150
'[DOCUMENT_FROM_FILE]: A document file name must have ".sdoc" extension: '151
f"{document_from_file.file}."
152
)153
if not os.path.isfile(resolved_path_to_fragment_file):
154
raise StrictDocException(
155
"[DOCUMENT_FROM_FILE]: Path to a document file does not exist: "156
f"{document_from_file.file}."
157
)158
159
document_from_file.ng_document_reference = (
160
self.parse_context.document_reference
161
)162
document_from_file.resolved_full_path_to_document_file = (
163
resolved_path_to_fragment_file164
)165
166
self.parse_context.fragments_from_files.append(document_from_file)
167
168
def process_requirement(self, requirement: SDocNode) -> None:
169
requirement.ng_document_reference = (
170
self.parse_context.document_reference
171
)172
requirement.ng_including_document_reference = (
173
self.parse_context.context_document_reference
174
)175
176
if requirement.is_normative_node():
177
self.parse_context.document_has_requirements = True
178
179
cursor: Union[SDocDocumentIF, SDocNodeIF] = requirement.parent
180
while (
181
isinstance(cursor, (SDocNodeIF))
182
and not cursor.ng_has_requirements
183
):184
cursor.ng_has_requirements = True
185
cursor = cursor.parent
186
187
assert self.parse_context.document_config is not None
188
if (
189
requirement.reserved_title is None
190
or not self.parse_context.document_config.is_requirement_in_toc()
191
) and self.parse_context.document_config.auto_levels:
192
requirement.ng_resolved_custom_level = "None"
193
194
preserve_source_location_data(requirement)
195
196
# FIXME: Refactor to eliminate the need in such assert.197
assert self.parse_context.document_config is not None
198
199
if not self.parse_context.document_config.auto_levels:
200
if not requirement.ng_resolved_custom_level:
201
raise StrictDocException(
202
f"[{requirement.node_type}].LEVEL field is not provided. "
203
"This contradicts to the option "204
"[DOCUMENT].OPTIONS.AUTO_LEVELS set to Off. "205
f"Node: {requirement}"
206
)207
208
def process_node_field(self, node_field: SDocNodeField) -> None:
209
node_field_parts = node_field.parts
210
if (
211
isinstance(node_field_parts[0], str)
212
and node_field_parts[0].strip() == ""
213
):214
the_model = get_model(node_field)
215
line_start, col_start = the_model._tx_parser.pos_to_linecol(
216
cast(SupportsTxPosition, node_field)._tx_position
217
)218
raise TextXSyntaxError(
219
"Node statement cannot be empty.",
220
line=line_start,
221
col=col_start,
222
filename=self.parse_context.path_to_sdoc_file,
223
)