File: cgbase.py

package info (click to toggle)
python-tatsu 5.15.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 904 kB
  • sloc: python: 10,128; makefile: 54
file content (159 lines) | stat: -rw-r--r-- 4,517 bytes parent folder | download
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))