160 lines
4.4 KiB
Python
160 lines
4.4 KiB
Python
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))
|