File: rendering.py

package info (click to toggle)
python-tatsu 5.15.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 904 kB
  • sloc: python: 10,128; makefile: 54
file content (141 lines) | stat: -rw-r--r-- 3,919 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
"""
The Renderer class provides the infrastructure for generating template-based
code. It's used by the .grammars module for parser generation.
"""
from __future__ import annotations

import itertools
import string

from tatsu.util import indent, isiter, trim


def render(item, join='', **fields):
    """Render the given item"""
    if item is None:
        return ''
    elif isinstance(item, str):
        return item
    elif isinstance(item, Renderer):
        return item.render(join=join, **fields)
    elif isiter(item):
        return join.join(
            render(e, **fields) for e in iter(item) if e is not None
        )
    elif isinstance(item, int | float):
        return item
    else:
        return str(item)


class RenderingFormatter(string.Formatter):
    def render(self, item, join='', **fields):
        return render(item, join=join, **fields)

    def format_field(self, value, format_spec):
        if ':' not in format_spec:
            return super().format_field(self.render(value), format_spec)

        ind, sep, fmt = format_spec.split(':')
        if sep == '\\n':
            sep = '\n'

        if not ind:
            ind = 0
            mult = 0
        elif '*' in ind:
            ind, mult = ind.split('*')
        else:
            mult = 4
        ind = int(ind)
        mult = int(mult)

        if not fmt:
            fmt = '%s'

        if isiter(value):
            return indent(
                sep.join(fmt % self.render(v) for v in value), ind, mult,
            )
        else:
            return indent(fmt % self.render(value), ind, mult)


class Renderer:
    """Renders the fileds in the current object using a template
    provided statically, on the constructor, or as a parameter
    to render().

    Fields with a leading underscore are not made available to
    the template. Additional fields may be made available by
    overriding render_fields().
    """

    template = '{__class__}'
    _counter = itertools.count()
    _formatter = RenderingFormatter()

    def __init__(self, template=None):
        if template is not None:
            self.template = template

    @classmethod
    def counter(cls):
        return next(cls._counter)

    @classmethod
    def reset_counter(cls):
        Renderer._counter = itertools.count()

    @property
    def formatter(self):
        return self._formatter

    @formatter.setter
    def formatter(self, value):
        self._formatter = value

    def rend(self, item, join='', **fields):
        """A shortcut for self._formatter.render()"""
        return self._formatter.render(item, join=join, **fields)

    def indent(self, item, ind=1, multiplier=4):
        return indent(self.rend(item), indent=ind, multiplier=multiplier)

    def trim(self, item, tabwidth=4):
        return trim(self.rend(item), tabwidth=tabwidth)

    def render_fields(self, fields):
        """Pre-render fields before rendering the template."""
        return

    def render(self, **fields):
        template = fields.pop('template', None)
        fields.update(__class__=self.__class__.__name__)
        fields.update(
            {k: v for k, v in vars(self).items() if not k.startswith('_')},
        )

        override = self.render_fields(
            fields,
        )  # pylint: disable=assignment-from-none
        if override is not None:
            template = override
        elif template is None:
            template = self.template

        try:
            return self._formatter.format(trim(template), **fields)
        except KeyError as e:
            # find the missing key
            keys = (p[1] for p in self._formatter.parse(template))
            for key in keys:
                if key and key not in fields:
                    raise KeyError(key, type(self)) from e
            raise

    def __str__(self):
        return self.render()

    def __repr__(self):
        return str(self)