11
22from abc import ABC , abstractmethod
3+ from copy import deepcopy
34from rlc .layout import Layout , Direction , FIT , Padding
45from typing import Dict
5- from dataclasses import dataclass , field
6+ from dataclasses import dataclass , field , fields , is_dataclass , MISSING
67from rlc .text import Text
8+ import yaml
79
810_renderer_registry = {} # maps class name → class
911
12+ class RenderableDumper (yaml .SafeDumper ):
13+ index = 0
14+ def generate_anchor (self , node : yaml .Node ):
15+
16+ self .index = self .index + 1
17+ return str (self .index )
18+
19+ class RenderableLoader (yaml .FullLoader ):
20+ pass
21+
22+ def renderable_representer (dumper : RenderableDumper , obj : 'Renderable' ):
23+ tag = obj .yaml_tag ()
24+ mapping = []
25+ for f in fields (obj ):
26+ value = getattr (obj , f .name )
27+
28+ if f .default is not MISSING and value == f .default :
29+ continue
30+
31+ if f .default_factory is not MISSING :
32+ try :
33+ default = f .default_factory ()
34+ if value == default :
35+ continue
36+ except TypeError :
37+ pass
38+
39+ mapping .append ((f .name , value ))
40+ return dumper .represent_mapping (tag , mapping )
41+
42+
43+ def renderable_multi_constructor (loader : RenderableLoader , tag_suffix : str , node ):
44+ """
45+ tag_suffix is the part after the '!' when using add_multi_constructor("!", ...)
46+ e.g. YAML tag `!FooRenderer` → tag_suffix == "FooRenderer"
47+ """
48+ cls = _renderer_registry [tag_suffix ] # look up the class
49+ data = loader .construct_mapping (node , deep = True )
50+ return cls (** data )
51+
52+ yaml .add_multi_constructor ("!" , renderable_multi_constructor , Loader = RenderableLoader )
53+
1054def register_renderer (cls ):
1155 _renderer_registry [cls .__name__ ] = cls
1256 return cls
1357
14- @dataclass ( kw_only = True )
58+ @dataclass
1559class Renderable (ABC ):
1660 """
1761 Base abstract renderer type.
1862 Each subclasss knows how to convert its types object into a Layout tree.
1963 """
20- rlc_type_name : str
21- style_policy : Dict = field (default_factory = dict )
2264
2365 def make_layout (self , direction = Direction .COLUMN , color = "white" , sizing = (FIT (), FIT ()), logger = None , padding = Padding (2 ,2 ,2 ,2 ), border = 3 , child_gap = 5 ) -> Layout :
2466 layout = Layout (sizing = sizing , direction = direction , color = color , padding = padding , border = border , child_gap = child_gap )
2567
26- for attr , value in self .style_policy .items ():
27- if value is not None and hasattr (layout , attr ):
28- setattr (layout , attr , value )
29-
3068 return layout
3169
3270 def make_text (self , txt , font_name , font_size , color ) -> Text :
3371 text = Text (txt , font_name , font_size , color )
34-
35- for attr , value in self .style_policy .items ():
36- if value is not None and hasattr (text , attr ):
37- setattr (text , attr , value )
38-
3972 return text
4073
4174 @abstractmethod
@@ -59,7 +92,6 @@ def __call__(self, obj, parent_binding=None, **kwds):
5992 # If build_layout didn't set binding, ensure we add it
6093 if layout .binding is None :
6194 layout .binding = {
62- "type" : self .rlc_type_name ,
6395 "parent" : parent_binding ,
6496 }
6597 else :
@@ -78,7 +110,34 @@ def __call__(self, obj, parent_binding=None, **kwds):
78110
79111 return layout
80112
113+ def post_order_types (self ):
114+ frontier = [self ]
115+ seen = set ()
116+ output = []
117+ while len (frontier ) != 0 :
118+ current = frontier .pop (0 )
119+ if id (current ) in seen :
120+ continue
121+ output .append (current )
122+ seen .add (id (current ))
123+ for child in current ._iter_children ():
124+ frontier .append (child )
125+ return [x for x in reversed (output )]
126+
127+
128+ def to_yaml (self ):
129+ return yaml .dump (self .post_order_types (), Dumper = RenderableDumper , sort_keys = False )
130+
131+ @classmethod
132+ def from_yaml (cls , yaml_text ):
133+ return yaml .load (yaml_text , Loader = RenderableLoader )[- 1 ]
134+
135+ @classmethod
136+ def yaml_tag (cls ) -> str :
137+ return f"!{ cls .__name__ } "
81138
82139 def _iter_children (self ):
83140 """Return iterable of child renderers, if any. Override per subclass."""
84141 return []
142+
143+ yaml .add_multi_representer (Renderable , renderable_representer , Dumper = RenderableDumper )
0 commit comments