1717from strictdoc .backend .sdoc .document_reference import DocumentReference
1818from strictdoc .backend .sdoc .error_handling import StrictDocSemanticError
1919from strictdoc .backend .sdoc .models .document import SDocDocument
20+ from strictdoc .backend .sdoc .models .document_grammar import (
21+ DocumentGrammar ,
22+ )
2023from strictdoc .backend .sdoc .models .model import SDocDocumentIF , SDocNodeIF
2124from strictdoc .backend .sdoc .models .node import SDocNode
2225from strictdoc .backend .sdoc .models .reference import FileEntry , FileReference
3740 RelationMarkerType ,
3841 SourceFileTraceabilityInfo ,
3942)
43+ from strictdoc .backend .sdoc_source_code .models .source_node import SourceNode
4044from strictdoc .core .constants import GraphLinkType
4145from strictdoc .core .document_iterator import SDocDocumentIterator
42- from strictdoc .core .project_config import ProjectConfig
46+ from strictdoc .core .project_config import ProjectConfig , SourceNodesEntry
4347from strictdoc .core .source_tree import SourceFile
4448from strictdoc .helpers .cast import assert_cast
4549from strictdoc .helpers .exception import StrictDocException
@@ -592,9 +596,11 @@ def create_folder_section(
592596 documents_with_generated_content = set ()
593597
594598 section_cache = {}
595- source_nodes_config : List [Dict [str , str ]] = project_config .source_nodes
599+ source_nodes_config : List [SourceNodesEntry ] = (
600+ project_config .source_nodes
601+ )
596602 unused_source_node_paths = {
597- config_entry_ [ " path" ] for config_entry_ in source_nodes_config
603+ config_entry_ . path for config_entry_ in source_nodes_config
598604 }
599605 for (
600606 path_to_source_file_ ,
@@ -606,16 +612,19 @@ def create_folder_section(
606612 if len (source_nodes_config ) == 0 :
607613 continue
608614
609- for config_entry_ in source_nodes_config :
610- config_entry_path = config_entry_ ["path" ]
611- if path_to_source_file_ .startswith (config_entry_path ):
612- relevant_source_node_entry = config_entry_
613- unused_source_node_paths .discard (config_entry_path )
614- break
615+ relevant_source_node_entry = (
616+ project_config .get_relevant_source_nodes_entry (
617+ path_to_source_file_
618+ )
619+ )
620+ if relevant_source_node_entry is not None :
621+ unused_source_node_paths .discard (
622+ relevant_source_node_entry .path
623+ )
615624 else :
616625 continue
617626
618- document_uid = relevant_source_node_entry [ " uid" ]
627+ document_uid = relevant_source_node_entry . uid
619628 document = traceability_index .get_node_by_uid (document_uid )
620629 documents_with_generated_content .add (document )
621630
@@ -638,95 +647,43 @@ def create_folder_section(
638647 current_top_node = section_cache [path_component_ ]
639648
640649 for source_node_ in traceability_info_ .source_nodes :
641- source_sdoc_node = SDocNode (
642- parent = document ,
643- node_type = relevant_source_node_entry ["node_type" ],
644- fields = [],
645- relations = [],
646- # It is important that this autogenerated node is marked as such.
647- autogen = True ,
648- )
649650 assert source_node_ .entity_name is not None
650651 source_sdoc_node_uid = f"{ document_uid } /{ path_to_source_file_ } /{ source_node_ .entity_name } "
651- source_sdoc_node .ng_document_reference = DocumentReference ()
652- source_sdoc_node .ng_document_reference .set_document (document )
653- source_sdoc_node .ng_including_document_reference = (
654- DocumentReference ()
655- )
656- source_sdoc_node .set_field_value (
657- field_name = "UID" ,
658- form_field_index = 0 ,
659- value = source_sdoc_node_uid ,
660- )
661- source_sdoc_node .set_field_value (
662- field_name = "TITLE" ,
663- form_field_index = 0 ,
664- value = source_node_ .entity_name ,
665- )
666652
667- for node_name_ , node_value_ in source_node_ .fields .items ():
668- source_sdoc_node .set_field_value (
669- field_name = node_name_ ,
670- form_field_index = 0 ,
671- value = node_value_ ,
653+ source_sdoc_node = traceability_index .get_node_by_uid_weak (
654+ source_sdoc_node_uid
655+ )
656+ if source_sdoc_node is not None :
657+ source_sdoc_node = assert_cast (source_sdoc_node , SDocNode )
658+ self .merge_sdoc_node_with_source_node (
659+ source_sdoc_node ,
660+ source_node_ ,
661+ document ,
662+ relevant_source_node_entry ,
663+ )
664+ else :
665+ source_sdoc_node = self .create_sdoc_node_from_source_node (
666+ source_node_ ,
667+ source_sdoc_node_uid ,
668+ document ,
669+ relevant_source_node_entry ,
670+ )
671+ current_top_node .section_contents .append (source_sdoc_node )
672+ traceability_index .graph_database .create_link (
673+ link_type = GraphLinkType .UID_TO_NODE ,
674+ lhs_node = source_sdoc_node_uid ,
675+ rhs_node = source_sdoc_node ,
672676 )
673- current_top_node .section_contents .append (source_sdoc_node )
674677
675- traceability_index .graph_database .create_link (
676- link_type = GraphLinkType .UID_TO_NODE ,
677- lhs_node = source_sdoc_node_uid ,
678- rhs_node = source_sdoc_node ,
678+ self .connect_source_node_function (
679+ source_node_ , source_sdoc_node_uid , traceability_info_
679680 )
680-
681- source_node_function = source_node_ .function
682- assert source_node_function is not None
683-
684- function_marker = self .forward_function_marker_from_function (
685- function = source_node_function ,
686- marker_type = RangeMarkerType .FUNCTION ,
687- reqs = [Req (None , source_sdoc_node_uid )],
688- role = None ,
689- description = f"function { source_node_function .display_name } ()" ,
681+ self .connect_sdoc_node_with_file_path (
682+ source_sdoc_node , path_to_source_file_
683+ )
684+ self .connect_source_node_requirements (
685+ source_node_ , source_sdoc_node , traceability_index
690686 )
691-
692- traceability_info_ .ng_map_reqs_to_markers .setdefault (
693- source_sdoc_node_uid , []
694- ).append (function_marker )
695-
696- self .map_reqs_uids_to_paths .setdefault (
697- source_sdoc_node_uid , OrderedSet ()
698- ).add (path_to_source_file_ )
699-
700- self .map_paths_to_reqs .setdefault (
701- path_to_source_file_ , OrderedSet ()
702- ).add (source_sdoc_node )
703-
704- function_marker_copy = function_marker .create_end_marker ()
705-
706- traceability_info_ .markers .append (function_marker )
707- traceability_info_ .markers .append (function_marker_copy )
708-
709- #
710- # This connects:
711- # - Source nodes and auto-generated requirements.
712- # - Source nodes-related requirements and auto-generated requirements.
713- #
714- for marker_ in source_node_ .markers :
715- if not isinstance (marker_ , FunctionRangeMarker ):
716- continue
717-
718- for req_ in marker_ .reqs :
719- node = traceability_index .get_node_by_uid_weak2 (req_ )
720- traceability_index .graph_database .create_link (
721- link_type = GraphLinkType .NODE_TO_PARENT_NODES ,
722- lhs_node = source_sdoc_node ,
723- rhs_node = node ,
724- )
725- traceability_index .graph_database .create_link (
726- link_type = GraphLinkType .NODE_TO_CHILD_NODES ,
727- lhs_node = node ,
728- rhs_node = source_sdoc_node ,
729- )
730687
731688 # Warn if source_node was not matched by any include_source_paths, it indicates misconfiguration
732689 for unused_source_node_path in unused_source_node_paths :
@@ -990,3 +947,160 @@ def compare_sdocnode_by_uid(node_: SDocNode) -> str:
990947 return assert_cast (node_ .reserved_uid , str )
991948
992949 path_requirements_ .sort (key = compare_sdocnode_by_uid )
950+
951+ def connect_source_node_function (
952+ self ,
953+ source_node : SourceNode ,
954+ source_sdoc_node_uid : str ,
955+ traceability_info : SourceFileTraceabilityInfo ,
956+ ) -> None :
957+ source_node_function = source_node .function
958+ assert source_node_function is not None
959+
960+ function_marker = self .forward_function_marker_from_function (
961+ function = source_node_function ,
962+ marker_type = RangeMarkerType .FUNCTION ,
963+ reqs = [Req (None , source_sdoc_node_uid )],
964+ role = None ,
965+ description = f"function { source_node_function .display_name } ()" ,
966+ )
967+
968+ traceability_info .ng_map_reqs_to_markers .setdefault (
969+ source_sdoc_node_uid , []
970+ ).append (function_marker )
971+ function_marker_copy = function_marker .create_end_marker ()
972+ traceability_info .markers .append (function_marker )
973+ traceability_info .markers .append (function_marker_copy )
974+
975+ @staticmethod
976+ def create_sdoc_node_from_source_node (
977+ source_node : SourceNode ,
978+ uid : str ,
979+ parent_document : SDocDocumentIF ,
980+ relevant_source_node_entry : SourceNodesEntry ,
981+ ) -> SDocNode :
982+ source_sdoc_node = SDocNode (
983+ parent = parent_document ,
984+ node_type = relevant_source_node_entry .node_type ,
985+ fields = [],
986+ relations = [],
987+ # It is important that this autogenerated node is marked as such.
988+ autogen = True ,
989+ )
990+ source_sdoc_node .ng_document_reference = DocumentReference ()
991+ source_sdoc_node .ng_document_reference .set_document (parent_document )
992+ source_sdoc_node .ng_including_document_reference = DocumentReference ()
993+ source_sdoc_node .set_field_value (
994+ field_name = "UID" ,
995+ form_field_index = 0 ,
996+ value = uid ,
997+ )
998+ source_sdoc_node .set_field_value (
999+ field_name = "TITLE" ,
1000+ form_field_index = 0 ,
1001+ value = source_node .entity_name ,
1002+ )
1003+ for node_name_ , node_value_ in source_node .fields .items ():
1004+ source_sdoc_node .set_field_value (
1005+ field_name = node_name_ ,
1006+ form_field_index = 0 ,
1007+ value = node_value_ ,
1008+ )
1009+ return source_sdoc_node
1010+
1011+ @staticmethod
1012+ def merge_sdoc_node_with_source_node (
1013+ sdoc_node : SDocNode ,
1014+ source_node : SourceNode ,
1015+ parent_document : SDocDocumentIF ,
1016+ source_node_config_entry : SourceNodesEntry ,
1017+ ) -> None :
1018+ # First check if grammar element definitions are compatible.
1019+ source_node_type = source_node_config_entry .node_type
1020+ source_node_grammar = assert_cast (
1021+ parent_document .grammar , DocumentGrammar
1022+ )
1023+ source_node_grammar_element = source_node_grammar .elements_by_type [
1024+ source_node_type
1025+ ]
1026+ sdoc_node_document = assert_cast (
1027+ sdoc_node .get_document (), SDocDocumentIF
1028+ )
1029+ sdoc_node_grammar = assert_cast (
1030+ sdoc_node_document .grammar , DocumentGrammar
1031+ )
1032+ sdoc_node_grammar_element = sdoc_node_grammar .elements_by_type [
1033+ source_node_type
1034+ ]
1035+ if source_node_grammar_element != sdoc_node_grammar_element :
1036+ raise StrictDocException (
1037+ f"Can't merge node { sdoc_node .reserved_uid } with source portion: "
1038+ f"Grammar element { sdoc_node_document .reserved_uid } ::{ source_node_type } "
1039+ f"incompatible with { parent_document .reserved_uid } ::{ source_node_type } "
1040+ )
1041+ # Merge strategy: overwrite title if there's a TITLE from custom tags.
1042+ if "TITLE" in source_node .fields :
1043+ sdoc_node .set_field_value (
1044+ field_name = "TITLE" ,
1045+ form_field_index = 0 ,
1046+ value = source_node .fields ["TITLE" ],
1047+ )
1048+ # Merge strategy: overwrite any field if there's a field with same name from custom tags.
1049+ for node_name_ , node_value_ in source_node .fields .items ():
1050+ sdoc_node .set_field_value (
1051+ field_name = node_name_ ,
1052+ form_field_index = 0 ,
1053+ value = node_value_ ,
1054+ )
1055+
1056+ def connect_sdoc_node_with_file_path (
1057+ self , sdoc_node : SDocNode , path_to_source_file_ : str
1058+ ) -> None :
1059+ uid = sdoc_node .reserved_uid
1060+ assert uid is not None
1061+ self .map_reqs_uids_to_paths .setdefault (uid , OrderedSet ()).add (
1062+ path_to_source_file_
1063+ )
1064+ self .map_paths_to_reqs .setdefault (
1065+ path_to_source_file_ , OrderedSet ()
1066+ ).add (sdoc_node )
1067+
1068+ @staticmethod
1069+ def connect_source_node_requirements (
1070+ source_node : SourceNode ,
1071+ sdoc_node : SDocNode ,
1072+ traceability_index : "TraceabilityIndex" ,
1073+ ) -> None :
1074+ """
1075+ Connect auto-generated requirement with function marker and with marker target requirement.
1076+
1077+ If function comment has @relation(REQ, scope=function), connections shall become
1078+ [REQ] <-parent- [auto-generated/merged sdoc_node] -file-> [function marker]
1079+
1080+ Here we link REQ and sdoc_node bidirectional.
1081+ """
1082+ for marker_ in source_node .markers :
1083+ if not isinstance (marker_ , FunctionRangeMarker ):
1084+ continue
1085+ for req_ in marker_ .reqs :
1086+ node = traceability_index .get_node_by_uid_weak2 (req_ )
1087+ if not traceability_index .graph_database .has_link (
1088+ link_type = GraphLinkType .NODE_TO_PARENT_NODES ,
1089+ lhs_node = sdoc_node ,
1090+ rhs_node = node ,
1091+ ):
1092+ traceability_index .graph_database .create_link (
1093+ link_type = GraphLinkType .NODE_TO_PARENT_NODES ,
1094+ lhs_node = sdoc_node ,
1095+ rhs_node = node ,
1096+ )
1097+ if not traceability_index .graph_database .has_link (
1098+ link_type = GraphLinkType .NODE_TO_CHILD_NODES ,
1099+ lhs_node = node ,
1100+ rhs_node = sdoc_node ,
1101+ ):
1102+ traceability_index .graph_database .create_link (
1103+ link_type = GraphLinkType .NODE_TO_CHILD_NODES ,
1104+ lhs_node = node ,
1105+ rhs_node = sdoc_node ,
1106+ )
0 commit comments