1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
from __future__ import annotations
from ..exceptions import CodegenError
from ..objectmodel import Node
from .rendering import Renderer, RenderingFormatter, render
__all__ = [
'DelegatingRenderingFormatter',
'ModelRenderer',
'NullModelRenderer',
'CodeGenerator',
]
class DelegatingRenderingFormatter(RenderingFormatter):
def __init__(self, delegate):
assert hasattr(delegate, 'render')
super().__init__()
self.delegate = delegate
# override
def render(self, item, join='', **fields):
result = self.delegate.render(item, join=join, **fields)
if result is None:
result = super().render(item, join=join, **fields)
return result
def convert_field(self, value, conversion):
if isinstance(value, Node):
return self.render(value)
else:
return super().convert_field(value, conversion)
class ModelRenderer(Renderer):
def __init__(self, codegen, node, template=None):
super().__init__(template=template)
self._codegen = codegen
self._node = node
self.formatter = codegen.formatter
self.__postinit__()
def __postinit__(self):
pass
def __getattr__(self, name):
try:
super().__getattr__(name)
except AttributeError:
if name.startswith('_'):
raise
return getattr(self.node, name)
@property
def node(self):
return self._node
@property
def codegen(self):
return self._codegen
@property
def context(self):
return self._codegen
def get_renderer(self, item):
return self.codegen.get_renderer(item)
def render(self, **fields):
template = fields.pop('template', None)
if isinstance(self.node, Node):
fields.update(
{
k: v
for k, v in vars(self.node).items()
if not k.startswith('_')
},
)
else:
fields.update(value=self.node)
return super().render(template=template, **fields)
class NullModelRenderer(ModelRenderer):
"""A `ModelRenderer` that generates nothing."""
template = ''
class CodeGenerator:
"""
A **CodeGenerator** is an abstract class that finds a
``ModelRenderer`` class with the same name as each model's node and
uses it to render the node.
"""
def __init__(self, modules=None):
self.formatter = DelegatingRenderingFormatter(self)
self._renderers = {}
if modules is not None:
self._renderers = self._find_module_renderers(modules)
def _find_module_renderers(self, modules):
result = {}
for module in modules:
for name, dtype in vars(module).items():
if not isinstance(dtype, type):
continue
if not issubclass(dtype, ModelRenderer):
continue
if dtype is not ModelRenderer:
result[name] = dtype
return result
def _find_renderer_class(self, node):
if not isinstance(node, Node):
return None
node_class_name = node.__class__.__name__
classes = [node.__class__]
while classes:
cls = classes.pop()
name = cls.__name__
if name in self._renderers:
renderer = self._renderers[name]
self._renderers[node_class_name] = renderer
return renderer
for base in cls.__bases__:
if base not in classes:
classes.append(base)
raise CodegenError(f'Renderer for {node_class_name} not found')
def get_renderer(self, item):
if not isinstance(item, Node):
return None
renderer_class = self._find_renderer_class(item)
if renderer_class is None:
raise CodegenError(
f'Renderer not found for {type(item).__name__}',
)
try:
assert issubclass(renderer_class, ModelRenderer)
return renderer_class(self, item)
except Exception as e:
raise type(e)(str(e), renderer_class.__name__) from e
def render(self, item, join='', **fields):
renderer = self.get_renderer(item)
if renderer is None:
return render(item, join=join, **fields)
return str(renderer.render(**fields))
|