Path:
strictdoc/backend/sdoc/reader.py
Lines:
142
Non-empty lines:
117
Non-empty lines covered with requirements:
117 / 117 (100.0%)
Functions:
7
Functions covered by requirements:
7 / 7 (100.0%)
- "2.5. One document per one SDoc file" (REQUIREMENT)
- "2.1. SDoc markup language" (REQUIREMENT)
1
"""2
@relation(SDOC-SRS-105, scope=file)3
"""4
5
from copy import copy
6
from typing import Optional, Tuple
7
8
from textx import TextXSemanticError, TextXSyntaxError, metamodel_from_str
9
10
from strictdoc.backend.sdoc.grammar.grammar_builder import SDocGrammarBuilder
11
from strictdoc.backend.sdoc.models.constants import DOCUMENT_MODELS
12
from strictdoc.backend.sdoc.models.document import (
13
SDocDocument,
14
)15
from strictdoc.backend.sdoc.models.document_grammar import DocumentGrammar
16
from strictdoc.backend.sdoc.models.node import (
17
SDocCompositeNode,
18
)19
from strictdoc.backend.sdoc.pickle_cache import PickleCache
20
from strictdoc.backend.sdoc.processor import ParseContext, SDocParsingProcessor
21
from strictdoc.core.project_config import ProjectConfig
22
from strictdoc.helpers.cast import assert_cast
23
from strictdoc.helpers.exception import StrictDocException
24
from strictdoc.helpers.file_system import file_open_read_utf8
25
from strictdoc.helpers.string import strip_bom
26
from strictdoc.helpers.textx import drop_textx_meta
27
28
29
class SDReader:
30
meta_model = metamodel_from_str(
31
SDocGrammarBuilder.create_grammar(),
32
classes=DOCUMENT_MODELS,
33
use_regexp_group=True,
34
)35
36
@staticmethod37
def _read(
38
input_string: str,
39
file_path: Optional[str] = None,
40
) -> Tuple[SDocDocument, ParseContext]:
41
input_string = strip_bom(input_string)
42
43
parse_context = ParseContext(path_to_sdoc_file=file_path)
44
processor = SDocParsingProcessor(parse_context=parse_context)
45
SDReader.meta_model.register_obj_processors(
46
processor.get_default_processors()
47
)48
49
try:
50
document: SDocDocument = SDReader.meta_model.model_from_str(
51
input_string, file_name=file_path
52
)53
except (TextXSyntaxError, TextXSemanticError) as syntax_error_:
54
raise StrictDocException(
55
f"Could not parse file: "
56
f"{file_path}. "
57
f"Error: {syntax_error_.__class__.__name__}: {syntax_error_}"
58
) from syntax_error_
59
60
parse_context.document_reference.set_document(document)
61
document.ng_has_requirements = parse_context.document_has_requirements
62
63
return document, parse_context
64
65
@staticmethod66
def read(
67
input_string: str,
68
file_path: Optional[str] = None,
69
) -> SDocDocument:
70
document, _ = SDReader.read_with_parse_context(input_string, file_path)
71
return document
72
73
@staticmethod74
def read_with_parse_context(
75
input_string: str,
76
file_path: Optional[str] = None,
77
) -> Tuple[SDocDocument, ParseContext]:
78
document, parse_context = SDReader._read(input_string, file_path)
79
80
SDReader.fixup_composite_requirements(document)
81
82
return document, parse_context
83
84
def read_from_file(
85
self, file_path: str, project_config: ProjectConfig
86
) -> SDocDocument:
87
"""
88
Parse a provided .sdoc file and convert it into a Document object.89
"""90
91
unpickled_content = PickleCache.read_from_cache(
92
file_path, project_config, "sdoc"
93
)94
if unpickled_content:
95
return assert_cast(unpickled_content, SDocDocument)
96
97
with file_open_read_utf8(file_path) as file:
98
sdoc_content = file.read()
99
100
sdoc, parse_context = self.read_with_parse_context(
101
sdoc_content,
102
file_path=file_path,
103
)104
105
sdoc.fragments_from_files = parse_context.fragments_from_files
106
107
# HACK:108
# ProcessPoolExecutor doesn't work because of non-picklable parts109
# of textx. The offending fields are stripped down because they110
# are not used anyway.111
drop_textx_meta(sdoc)
112
113
# @relation(SDOC-SRS-155, scope=range_start)114
sdoc.build_search_index()
115
# @relation(SDOC-SRS-155, scope=range_end)116
117
PickleCache.save_to_cache(sdoc, file_path, project_config, "sdoc")
118
119
return sdoc
120
121
@staticmethod122
def fixup_composite_requirements(sdoc: SDocDocument) -> None:
123
for _, node_ in enumerate(copy(sdoc.section_contents)):
124
if isinstance(node_, SDocCompositeNode):
125
SDReader.migration_sections_grammar(sdoc)
126
break127
128
@staticmethod129
def migration_sections_grammar(sdoc: SDocDocument) -> None:
130
grammar: DocumentGrammar = assert_cast(sdoc.grammar, DocumentGrammar)
131
section_element_exists = any(
132
element_.tag == "SECTION" for element_ in grammar.elements
133
)134
if not section_element_exists:
135
grammar.update_with_elements(
136
[137
DocumentGrammar.create_default_section_element(
138
grammar, enable_mid=sdoc.config.enable_mid or False
139
)140
]141
+ grammar.elements
142
)