StrictDoc Documentation
strictdoc/core/analyzers/document_uid_analyzer.py
Source file coverage
Path:
strictdoc/core/analyzers/document_uid_analyzer.py
Lines:
118
Non-empty lines:
106
Non-empty lines covered with requirements:
106 / 106 (100.0%)
Functions:
3
Functions covered by requirements:
3 / 3 (100.0%)
1
import typing
2
from collections import Counter
3
from typing import Dict, List
4
 
5
from strictdoc.backend.sdoc.models.document import SDocDocument
6
from strictdoc.backend.sdoc.models.node import SDocNode
7
from strictdoc.core.analyzers.document_stats import (
8
    DocumentStats,
9
    DocumentTreeStats,
10
    SinglePrefixRequirements,
11
)
12
from strictdoc.core.document_iterator import SDocDocumentIterator
13
from strictdoc.core.document_tree import DocumentTree
14
from strictdoc.core.traceability_index import TraceabilityIndex
15
from strictdoc.helpers.cast import assert_cast
16
from strictdoc.helpers.string import (
17
    extract_last_numeric_part,
18
    extract_numeric_uid_prefix_part,
19
)
20
 
21
 
22
class DocumentUIDAnalyzer:
23
    @staticmethod
24
    def analyze_document_tree(
25
        traceability_index: TraceabilityIndex,
26
    ) -> DocumentTreeStats:
27
        global_requirements_per_prefix: Dict[str, SinglePrefixRequirements] = {}
28
        document_tree_stats: List[DocumentStats] = []
29
        section_uids_so_far: typing.Counter[str] = Counter()
30
 
31
        document_tree: DocumentTree = assert_cast(
32
            traceability_index.document_tree, DocumentTree
33
        )
34
 
35
        for document in document_tree.document_list:
36
            document_stats: DocumentStats = (
37
                DocumentUIDAnalyzer.analyze_document(document)
38
            )
39
            for (
40
                requirement_prefix_,
41
                prefix_requirements_,
42
            ) in document_stats.requirements_per_prefix.items():
43
                global_prefix_requirements = (
44
                    global_requirements_per_prefix.setdefault(
45
                        requirement_prefix_, SinglePrefixRequirements()
46
                    )
47
                )
48
                global_prefix_requirements.requirements_no_uid.extend(
49
                    prefix_requirements_.requirements_no_uid
50
                )
51
                global_prefix_requirements.requirements_uid_numbers.extend(
52
                    prefix_requirements_.requirements_uid_numbers
53
                )
54
            document_tree_stats.append(document_stats)
55
            for section_uid_ in document_stats.section_uids_so_far:
56
                section_uids_so_far[section_uid_] += 1
57
        return DocumentTreeStats(
58
            single_document_stats=document_tree_stats,
59
            requirements_per_prefix=global_requirements_per_prefix,
60
            section_uids_so_far=section_uids_so_far,
61
        )
62
 
63
    @staticmethod
64
    def analyze_document(
65
        document: SDocDocument,
66
    ) -> DocumentStats:
67
        this_document_stats = DocumentStats(document)
68
        document_iterator = SDocDocumentIterator(document)
69
        for node, _ in document_iterator.all_content():
70
            if isinstance(node, SDocNode) and node.node_type == "SECTION":
71
                if node.reserved_uid is not None:
72
                    this_document_stats.section_uids_so_far[
73
                        node.reserved_uid
74
                    ] += 1
75
                else:
76
                    this_document_stats.sections_without_uid.append(node)
77
                continue
78
            if not isinstance(node, SDocNode):
79
                continue
80
            if node.node_type in ("TEXT", "SECTION"):
81
                continue
82
            requirement: SDocNode = node
83
            node_prefix: typing.Optional[str] = requirement.get_prefix()
84
            if node_prefix is None:
85
                continue
86
 
87
            if node_prefix not in this_document_stats.requirements_per_prefix:
88
                this_document_stats.requirements_per_prefix[node_prefix] = (
89
                    SinglePrefixRequirements()
90
                )
91
 
92
            prefix_requirements: SinglePrefixRequirements = (
93
                this_document_stats.requirements_per_prefix[node_prefix]
94
            )
95
            if requirement.reserved_uid is None:
96
                prefix_requirements.requirements_no_uid.append(requirement)
97
            else:
98
                requirement_prefix = extract_numeric_uid_prefix_part(
99
                    requirement.reserved_uid
100
                )
101
 
102
                # If the requirement has a prefix which is different to that of
103
                # the parent section/document's prefix, we ignore this node as
104
                # incredible for contributing to the next UID calculation.
105
                if requirement_prefix != node_prefix:
106
                    continue
107
 
108
                requirement_uid_numeric_part = extract_last_numeric_part(
109
                    requirement.reserved_uid
110
                )
111
                assert len(requirement_uid_numeric_part) > 0
112
                requirement_uid_number = int(requirement_uid_numeric_part)
113
 
114
                prefix_requirements.requirements_uid_numbers.append(
115
                    requirement_uid_number
116
                )
117
 
118
        return this_document_stats