add proxy for the first part and try to develop the second part
This commit is contained in:
@@ -0,0 +1,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))
|
||||
Reference in New Issue
Block a user