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)
|