Skip to content

Commit 6b44b62

Browse files
authored
Merge pull request #2553 from strictdoc-project/stanislaw/relation_field
feat(traceability_index): allow linking nodes via MID
2 parents 6c5bcc0 + 6bfbf81 commit 6b44b62

File tree

11 files changed

+113
-19
lines changed

11 files changed

+113
-19
lines changed

strictdoc/backend/sdoc/grammar/grammar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
('ROOT: ' (root = BooleanChoice) '\n')?
8181
('OPTIONS:' '\n'
8282
(' ENABLE_MID: ' (enable_mid = BooleanChoice) '\n')?
83+
(' RELATION_FIELD: ' (relation_field = SingleLineString) '\n')?
8384
(' MARKUP: ' (markup = MarkupChoice) '\n')?
8485
(' AUTO_LEVELS: ' (auto_levels = AutoLevelsChoice) '\n')?
8586
(' LAYOUT: ' (layout = LayoutChoice) '\n')?

strictdoc/backend/sdoc/models/document_config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def default_config(document: Optional["SDocDocument"]) -> "DocumentConfig":
5050
requirement_prefix=None,
5151
root=None,
5252
enable_mid=None,
53+
relation_field=None,
5354
markup=None,
5455
auto_levels=None,
5556
layout=None,
@@ -70,6 +71,7 @@ def __init__(
7071
requirement_prefix: Optional[str],
7172
root: Optional[str],
7273
enable_mid: Optional[str],
74+
relation_field: Optional[str],
7375
markup: Optional[str],
7476
auto_levels: Optional[str],
7577
layout: Optional[str],
@@ -100,6 +102,11 @@ def __init__(
100102
if enable_mid == "True"
101103
else (False if root == "False" else None)
102104
)
105+
self.relation_field: str = (
106+
relation_field
107+
if relation_field is not None and len(relation_field) > 0
108+
else "UID"
109+
)
103110

104111
self.markup: Optional[str] = markup
105112
self.auto_levels: bool = auto_levels is None or auto_levels == "On"

strictdoc/backend/sdoc/models/node.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from strictdoc.backend.sdoc.document_reference import DocumentReference
99
from strictdoc.backend.sdoc.models.anchor import Anchor
10+
from strictdoc.backend.sdoc.models.document_config import DocumentConfig
1011
from strictdoc.backend.sdoc.models.document_grammar import (
1112
DocumentGrammar,
1213
)
@@ -305,14 +306,20 @@ def has_any_text_nodes(self) -> bool:
305306

306307
@property
307308
def reserved_uid(self) -> Optional[str]:
309+
document = assert_cast(self.get_document(), SDocDocumentIF)
310+
config = assert_cast(document.config, DocumentConfig)
311+
308312
return self._get_cached_field(
309-
RequirementFieldName.UID, singleline_only=True
313+
config.relation_field, singleline_only=True
310314
)
311315

312316
@reserved_uid.setter
313317
def reserved_uid(self, uid: Optional[str]) -> None:
318+
document = assert_cast(self.get_document(), SDocDocumentIF)
319+
config = assert_cast(document.config, DocumentConfig)
320+
314321
self.set_field_value(
315-
field_name="UID",
322+
field_name=config.relation_field,
316323
form_field_index=0,
317324
value=uid,
318325
)
@@ -407,8 +414,8 @@ def get_debug_info(self) -> str:
407414
debug_components: List[str] = []
408415
if self.reserved_mid is not None:
409416
debug_components.append(f"MID = '{self.reserved_mid}'")
410-
if self.reserved_uid is not None:
411-
debug_components.append(f"UID = '{self.reserved_uid}'")
417+
if (reserved_uid_ := self.reserved_uid) is not None:
418+
debug_components.append(f"UID = '{reserved_uid_}'")
412419
if self.reserved_title is not None:
413420
debug_components.append(f"TITLE = '{self.reserved_title}'")
414421

strictdoc/core/file_traceability_index.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,6 @@ def validate_and_resolve(
456456
reqs_uids,
457457
)
458458

459-
validated_requirement_uids: Set[str] = set()
460459
marker_: Union[
461460
FunctionRangeMarker, LineMarker, RangeMarker, ForwardRangeMarker
462461
]
@@ -465,29 +464,23 @@ def validate_and_resolve(
465464
if isinstance(marker_, ForwardRangeMarker):
466465
continue
467466
for requirement_uid_ in marker_.reqs:
468-
if requirement_uid_ not in validated_requirement_uids:
469-
node = traceability_index.get_node_by_uid_weak2(
470-
requirement_uid_
467+
node = traceability_index.get_node_by_uid_weak2(
468+
requirement_uid_
469+
)
470+
if node is None:
471+
raise StrictDocException(
472+
f"Source file {source_file.in_doctree_source_file_rel_path_posix} references "
473+
f"a requirement that does not exist: {requirement_uid_}."
471474
)
472-
if node is None:
473-
raise StrictDocException(
474-
f"Source file {source_file.in_doctree_source_file_rel_path_posix} references "
475-
f"a requirement that does not exist: {requirement_uid_}."
476-
)
477-
validated_requirement_uids.add(requirement_uid_)
478475

479476
self.map_reqs_uids_to_paths.setdefault(
480477
requirement_uid_, OrderedSet()
481478
).add(source_file.in_doctree_source_file_rel_path_posix)
482479

483-
node_id = traceability_index.get_node_by_uid(
484-
requirement_uid_
485-
)
486-
487480
self.map_paths_to_reqs.setdefault(
488481
source_file.in_doctree_source_file_rel_path_posix,
489482
OrderedSet(),
490-
).add(node_id)
483+
).add(node)
491484

492485
if isinstance(marker_, FunctionRangeMarker):
493486
marker_copy = marker_.create_end_marker()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[DOCUMENT]
2+
TITLE: Test document
3+
OPTIONS:
4+
ENABLE_MID: True
5+
RELATION_FIELD: MID
6+
7+
[REQUIREMENT]
8+
MID: aaaaaaaa_aaaaaaaa_aaaaaaaa_aaaaaaaa
9+
TITLE: Requirement A title
10+
STATEMENT: Requirement A statement
11+
12+
[REQUIREMENT]
13+
MID: bbbbbbbb_bbbbbbbb_bbbbbbbb_bbbbbbbb
14+
TITLE: Requirement B title
15+
STATEMENT: Requirement B statement
16+
RELATIONS:
17+
- TYPE: Parent
18+
VALUE: aaaaaaaa_aaaaaaaa_aaaaaaaa_aaaaaaaa
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
RUN: %strictdoc export %S --output-dir %T | filecheck %s
2+
CHECK: Published: Test document
3+
4+
RUN: %cat %T/html/%THIS_TEST_FOLDER/input.html | filecheck %s --check-prefix CHECK-HTML
5+
6+
CHECK-HTML: RELATIONS (Child):
7+
CHECK-HTML: <a data-turbo="false" class="requirement__link-child" href="#bbbbbbbb_bbbbbbbb_bbbbbbbb_bbbbbbbb">
8+
CHECK-HTML: Requirement B title
9+
10+
CHECK-HTML: RELATIONS (Parent):
11+
CHECK-HTML: <a data-turbo="false" class="requirement__link-parent" href="#aaaaaaaa_aaaaaaaa_aaaaaaaa_aaaaaaaa">
12+
CHECK-HTML: Requirement A title
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def hello_world():
2+
"""
3+
Some text.
4+
5+
@relation(REQ-1, scope=function)
6+
"""
7+
# @relation(REQ-1, scope=range_start)
8+
print("hello world") # noqa: T201
9+
# @relation(REQ-1, scope=range_end)
10+
print("hello world") # noqa: T201
11+
# @relation(REQ-1, scope=range_start)
12+
print("hello world") # noqa: T201
13+
# @relation(REQ-1, scope=range_end)
14+
15+
def non_covered_function():
16+
pass
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[DOCUMENT]
2+
TITLE: Hello world doc
3+
OPTIONS:
4+
ENABLE_MID: True
5+
RELATION_FIELD: MID
6+
7+
[REQUIREMENT]
8+
MID: REQ-1
9+
TITLE: Requirement Title
10+
STATEMENT: Requirement Statement
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[project]
2+
3+
features = [
4+
"REQUIREMENT_TO_SOURCE_TRACEABILITY",
5+
"SOURCE_FILE_LANGUAGE_PARSERS",
6+
]
7+
8+
exclude_source_paths = [
9+
"test.itest",
10+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# @relation(SDOC-SRS-147, SDOC-SRS-137, scope=file)
2+
3+
RUN: %strictdoc --debug export %S --output-dir %T | filecheck %s --dump-input=fail
4+
CHECK: Published: Hello world doc
5+
6+
RUN: %check_exists --file "%T/html/_source_files/file.py.html"
7+
8+
RUN: %cat %T/html/%THIS_TEST_FOLDER/input.html | filecheck %s --check-prefix CHECK-HTML
9+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#1#13">
10+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#7#9">
11+
CHECK-HTML: <a{{.*}}href="../_source_files/file.py.html#REQ-1#11#13">
12+
13+
RUN: %cat %T/html/_source_files/file.py.html | filecheck %s --check-prefix CHECK-SOURCE-FILE
14+
CHECK-SOURCE-FILE: 1 - 13 | function hello_world()
15+
CHECK-SOURCE-FILE: 7 - 9 | range
16+
CHECK-SOURCE-FILE: 11 - 13 | range
17+
18+
RUN: %cat %T/html/source_coverage.html | filecheck %s --dump-input=fail --check-prefix CHECK-SOURCE-COVERAGE
19+
CHECK-SOURCE-COVERAGE: 85.7

0 commit comments

Comments
 (0)