StrictDoc Documentation
strictdoc/backend/sdoc/reader.py
Source file coverage
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%)
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
    @staticmethod
37
    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
    @staticmethod
66
    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
    @staticmethod
74
    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 parts
109
        # of textx. The offending fields are stripped down because they
110
        # 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
    @staticmethod
122
    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
                break
127
 
128
    @staticmethod
129
    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
            )