Coverage for src / graphable / views / asciiflow.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-16 21:32 +0000

1from dataclasses import dataclass 

2from logging import getLogger 

3from pathlib import Path 

4from typing import Any, Callable 

5 

6from ..graph import Graph 

7from ..graphable import Graphable 

8from ..registry import register_view 

9 

10logger = getLogger(__name__) 

11 

12 

13@dataclass 

14class AsciiflowStylingConfig: 

15 """ 

16 Configuration for ASCII flowchart representation of the graph. 

17 

18 Attributes: 

19 node_text_fnc: Function to generate the text representation of a node. 

20 show_tags: Whether to include tags in the output. 

21 """ 

22 

23 node_text_fnc: Callable[[Graphable[Any]], str] = lambda n: n.reference 

24 show_tags: bool = False 

25 

26 

27def create_topology_ascii_flow( 

28 graph: Graph, config: AsciiflowStylingConfig | None = None 

29) -> str: 

30 """ 

31 Create an ASCII-based flowchart representation of the graph. 

32 Unlike TextTree, this explicitly shows multiple parents by listing connections. 

33 

34 Args: 

35 graph (Graph): The graph to convert. 

36 config (AsciiflowStylingConfig | None): Styling configuration. 

37 

38 Returns: 

39 str: The ASCII flowchart representation. 

40 """ 

41 if config is None: 

42 config = AsciiflowStylingConfig() 

43 

44 logger.debug("Creating ASCII flowchart text.") 

45 

46 lines: list[str] = [] 

47 

48 # We'll group by "levels" using topological order to give a sense of flow 

49 nodes = graph.topological_order() 

50 

51 for node in nodes: 

52 node_text = config.node_text_fnc(node) 

53 tags_info = ( 

54 f" [{', '.join(node.tags)}]" if config.show_tags and node.tags else "" 

55 ) 

56 

57 # Box the node 

58 box_width = len(node_text + tags_info) + 2 

59 top_border = "+" + "-" * box_width + "+" 

60 middle = f"| {node_text}{tags_info} |" 

61 

62 lines.append(top_border) 

63 lines.append(middle) 

64 lines.append(top_border) 

65 

66 # Show dependencies (outgoing edges) 

67 internal_deps = list(graph.internal_dependents(node)) 

68 if internal_deps: 

69 for i, (dependent, _) in enumerate(internal_deps): 

70 dep_text = config.node_text_fnc(dependent) 

71 if i == 0: 

72 lines.append(f" v\n +--> {dep_text}") 

73 else: 

74 lines.append(f" +--> {dep_text}") 

75 

76 lines.append("") # Spacer between nodes 

77 

78 return "\n".join(lines) 

79 

80 

81@register_view(".ascii", creator_fnc=create_topology_ascii_flow) 

82def export_topology_ascii_flow( 

83 graph: Graph, output: Path, config: AsciiflowStylingConfig | None = None 

84) -> None: 

85 """ 

86 Export the graph to an ASCII flowchart file. 

87 

88 Args: 

89 graph (Graph): The graph to export. 

90 output (Path): The output file path. 

91 config (AsciiFlowStylingConfig | None): Styling configuration. 

92 """ 

93 logger.info(f"Exporting ASCII flowchart to: {output}") 

94 with open(output, "w+") as f: 

95 f.write(create_topology_ascii_flow(graph, config))