11from collections .abc import Iterator
2- from typing import TYPE_CHECKING , Optional , cast
2+ from typing import TYPE_CHECKING , Optional , Union , cast
33
44from eth_typing import ChecksumAddress
55from eth_utils import to_checksum_address
66
77from evm_trace .enums import CallType
88
99if TYPE_CHECKING :
10- from evm_trace .base import CallTreeNode
10+ from evm_trace .base import CallTreeNode , EventNode
1111
1212
1313def get_tree_display (call : "CallTreeNode" ) -> str :
1414 return "\n " .join ([str (t ) for t in TreeRepresentation .make_tree (call )])
1515
1616
1717class TreeRepresentation :
18- FILE_MIDDLE_PREFIX = "├──"
19- FILE_LAST_PREFIX = "└──"
18+ """
19+ A class for creating a simple tree-representation of a call-tree node.
20+
21+ **NOTE**: We purposely are not using the rich library here to keep
22+ evm-trace small and simple while sill offering a nice stringified
23+ version of a :class:`~evm_trace.base.CallTreeNode`.
24+ """
25+
26+ MIDDLE_PREFIX = "├──"
27+ LAST_PREFIX = "└──"
2028 PARENT_PREFIX_MIDDLE = " "
2129 PARENT_PREFIX_LAST = "│ "
2230
2331 def __init__ (
2432 self ,
25- call : "CallTreeNode" ,
33+ call : Union [ "CallTreeNode" , "EventNode" ] ,
2634 parent : Optional ["TreeRepresentation" ] = None ,
2735 is_last : bool = False ,
2836 ):
@@ -32,13 +40,27 @@ def __init__(
3240
3341 @property
3442 def depth (self ) -> int :
43+ """
44+ The depth in the call tree, such as the
45+ number of calls deep.
46+ """
3547 return self .call .depth
3648
3749 @property
3850 def title (self ) -> str :
51+ """
52+ The title of the node representation, including address, calldata, and return-data.
53+ For event-nodes, it is mostly the selector string.
54+ """
3955 call_type = self .call .call_type .value
40- address_hex_str = self .call .address .hex () if self .call .address else None
4156
57+ if hasattr (self .call , "selector" ):
58+ # Is an Event-node
59+ selector = self .call .selector .hex () if self .call .selector else None
60+ return f"{ call_type } : { selector } "
61+ # else: Is a CallTreeNode
62+
63+ address_hex_str = self .call .address .hex () if self .call .address else None
4264 try :
4365 address = to_checksum_address (address_hex_str ) if address_hex_str else None
4466 except (ImportError , ValueError ):
@@ -77,33 +99,54 @@ def title(self) -> str:
7799 @classmethod
78100 def make_tree (
79101 cls ,
80- root : "CallTreeNode" ,
102+ root : Union [ "CallTreeNode" , "EventNode" ] ,
81103 parent : Optional ["TreeRepresentation" ] = None ,
82104 is_last : bool = False ,
83105 ) -> Iterator ["TreeRepresentation" ]:
106+ """
107+ Create a node representation object from a :class:`~evm_trace.base.CallTreeNode`.
108+
109+ Args:
110+ root (:class:`~evm_trace.base.CallTreeNode` | :class:`~evm_trace.base.EventNode`):
111+ The call-tree node or event-node to display.
112+ parent (Optional[:class:`~evm_trace.display.TreeRepresentation`]): The parent
113+ node of this node.
114+ is_last (bool): True if a leaf-node.
115+ """
84116 displayable_root = cls (root , parent = parent , is_last = is_last )
85117 yield displayable_root
86-
87- count = 1
88- for child_node in root .calls :
89- is_last = count == len (root .calls )
90- if child_node .calls :
91- yield from cls .make_tree (child_node , parent = displayable_root , is_last = is_last )
92- else :
93- yield cls (child_node , parent = displayable_root , is_last = is_last )
94-
95- count += 1
118+ if hasattr (root , "topics" ):
119+ # Events have no children.
120+ return
121+
122+ # Handle events, which won't have any sub-calls or anything.
123+ total_events = len (root .events )
124+ for index , event in enumerate (root .events , start = 1 ):
125+ is_last = index == total_events
126+ yield cls (event , parent = displayable_root , is_last = is_last )
127+
128+ # Handle calls (and calls of calls).
129+ total_calls = len (root .calls )
130+ for index , child_node in enumerate (root .calls , start = 1 ):
131+ is_last = index == total_calls
132+ # NOTE: `.make_tree()` will handle calls of calls (recursion).
133+ yield from cls .make_tree (child_node , parent = displayable_root , is_last = is_last )
96134
97135 def __str__ (self ) -> str :
136+ """
137+ The representation str via ``calling str()``.
138+ """
98139 if self .parent is None :
99140 return self .title
100141
101- filename_prefix = self .FILE_LAST_PREFIX if self .is_last else self .FILE_MIDDLE_PREFIX
102-
103- parts = [f"{ filename_prefix } { self .title } " ]
142+ tree_prefix = self .LAST_PREFIX if self .is_last else self .MIDDLE_PREFIX
143+ parts = [f"{ tree_prefix } { self .title } " ]
104144 parent = self .parent
105145 while parent and parent .parent is not None :
106146 parts .append (self .PARENT_PREFIX_MIDDLE if parent .is_last else self .PARENT_PREFIX_LAST )
107147 parent = parent .parent
108148
109149 return "" .join (reversed (parts ))
150+
151+ def __repr__ (self ) -> str :
152+ return str (self )
0 commit comments