"""Mermaid diagram subclass and helper functions."""importloggingimportosimportrefromimportlib.metadataimportversionfromshutilimportwhichfromtypingimportList,Optionalfrompython_to_mermaidimportMermaidDiagram__version__=version('ds2mermaid')__all__=["MermaidGraph","SubGraph","__version__","check_for_doorstop","create_subgraph_diagram","get_doorstop_doc_tree",]
[docs]classSubGraph:# pylint: disable=too-few-public-methods""" A SubGraph class to represent mermaid subgraphs (ie, individual subgraph parts of a mermaid diagram). :param name: prefix label for subgraph :param nodes: list of prefix labels for subgraph nodes """def__init__(self,name:str,nodes:Optional[List]=None,):self.name=nameself.nodes=nodesor[]
[docs]defadd_node(self,node:str):"""Add a node label to the list of nodes"""self.nodes.append(node)
[docs]classMermaidGraph(MermaidDiagram):""" A mermaid subclass for generating subgraph diagrams. :param diagram_type: mermaid diagram type :param diagram_direction: mermaid graph direction :param subgraphs: list of doorstop prefix labels for subgraphs """def__init__(self,diagram_type:str="graph",diagram_direction:str="TB",subgraphs:Optional[List]=None,):super().__init__(diagram_type)self.subgraphs=subgraphsor[]self.set_direction(diagram_direction)
[docs]defadd_subgraph(self,subgraph:str):"""Add a subgraph label to the list of subgraphs"""self.subgraphs.append(subgraph)
[docs]defto_subgraph(self)->str:"""Convert the diagram to Mermaid syntax."""lines=[f"{self.diagram_type}{self.direction}"]forsubgraphinself.subgraphs:sub_begin_str=f" subgraph {subgraph}"lines.append(sub_begin_str)# Add nodesfornodein[xforxinself.nodesifsubgraphinstr(x.label)]:node_str=f" {node.id}"ifnode.shape:ifisinstance(node.shape,tuple):start,end=node.shapeelse:start,end=self.SHAPE_MAP[node.shape]node_str+=f"{start}{node.labelor''}{end}"elifnode.label:node_str+=f'["{node.label}"]'ifnode.style:style_str=",".join(f"{k}:{v}"fork,vinnode.style.items())node_str+=f" style {node.id}{style_str}"lines.append(node_str)sub_end_str=" end"lines.append(sub_end_str)# Add edgesforedgeinself.edges:edge_str=f" {edge.source}{edge.style}{edge.target}"ifedge.label:edge_str+=f"|{edge.label}|"lines.append(edge_str)returnf"{os.linesep}".join(lines)
[docs]defcheck_for_doorstop()->str:""" Make sure we can find the ``doorstop`` binary in the user environment and return a path string. :returns: program path string """doorstop_path:Optional[str]=Nonedoorstop_path=which('doorstop')ifnotdoorstop_path:logging.warning('Cannot continue, no path found for doorstop')raiseFileNotFoundError("doorstop not found in PATH")returndoorstop_path
[docs]defcreate_subgraph_diagram(prefixes:Optional[List]=None)->MermaidGraph:""" Create a new graph diagram with subgraphs. """returnMermaidGraph(subgraphs=prefixes)
[docs]defget_doorstop_doc_tree(tree:str)->List[str]:""" Parse the doorstop tree structure to get a list of document prefixes, starting with the root doc. The string repr looks like a simple ascii diagram:: tree <Tree TUI <- [ TST, SDD ]> str(tree) TUI <- [ TST, SDD ] :param tree: doorstop tree cast to a str :returns: list of document prefixes """slist:List=[]string=str(tree)idp=re.compile(r'[\[\-<>, \]]')slist=[xforxinidp.split(string)ifx!='']returnslist