2
__all__ = ['Component']
5
from joiner import Joiner
8
parts_re = re.compile(r'%\([\w.]+\)[rs]')
9
killline_re = re.compile(r'.*[<]KILLLINE[>].*(\n|$)')
12
""" Represents a branch of a Component tree.
14
def __init__(self, *items):
17
def __add__(self, other):
18
if isinstance(other, Component):
19
return type(self)(*(self.items + (other,)))
22
def get_attr(self, name, default=NotImplemented):
23
""" Search a branch component that contains attribute with
24
given name and return attribute value. If the name is in
25
a form ``clsname.attrname`` then the matching component must
26
have class name equal to ``clsname``. If attribute is not
27
found, raise an attribute error.
30
classname, attrname = name.split('.')
32
classname, attrname = None, name
33
for c in reversed(self.items):
34
if classname and classname!=type(c).__name__:
36
if hasattr(c, attrname):
37
return getattr(c, attrname)
38
if default is NotImplemented:
39
n = '-'.join([type(c).__name__ for c in self.items])
40
raise AttributeError("component branch %r does not have attribute with name %r" % (n, name))
43
def get_left(self, component):
46
parent = self.items[-1]
48
for leaf in parent.leafs:
54
def find_component_with_view(self, view_name, view_result):
57
return self.items[0].find_component_with_view(view_name, view_result)
59
class Component(object):
60
""" Base class for Component subclasses.
62
This class implements a certain combination of Composite
63
and Template method patterns.
65
A component contains data that are available as attributes. One
66
of the attributes is ``leafs`` that is a list of subcomponents.
67
Other attributes are defined by the definition of Component
70
A parent component can make data requests to its leafs. The data
71
will be used to fill out the component templates. The request
72
is done by submitting the following pair to a leafs:
74
#. the name of a view template part for which data is requested,
75
(leaves must know how to serve their parents).
77
#. a parent branch that provides parent data as well as the
78
possibility to access the data of neighboring leaves.
80
When the returned values from the component leafs are collected,
81
then the templates will be realized.
85
#. data requests are done with ``request_data(view_name,
86
parents)`` method where ``parents`` argument contains a parent branch.
88
#. a view template is a string containing ``%(...)s`` (so-called
89
the parts of a view template), components can define different
90
views in ``templates`` dictionary attribute.
92
#. the results of component data requestes are collected
93
in ``Joiner`` instances (see joiner.py).
95
#. the view is obtained by calling ``get_view(view_name, parents=Branch())``::
97
<view template> % dict(<name of view template part> = <Joiner instance>)
99
#. realization of a template is returned by the ``realize()`` method.
101
#. component classes can redefine the following methods:
103
#. ``__init__`` --- to define component specific data
106
#. ``request_data`` --- to connect leafs with parents
108
#. ``realize`` --- to return the final realization of a
109
component, it can be anything, including executing
114
# templates dictionary contains mappings:
115
# {<view name>:<template string>}
118
# template_options dictionary contains mappings:
119
# {<name of template part>:<options dictionary to Joiner constructor>}
120
template_options = dict()
122
# set True if get_view can call request_data:
123
request_own_data = False
125
def __init__(self, *leafs):
127
self._nof_saved_leafs = [0]
131
def _check_options(cls, options, *expected_names):
132
unknown_names = set(options).difference(expected_names)
134
raise ValueError('%s: unknown options %r' % (cls.__name__, unknown_names))
137
if isinstance(obj, Component):
138
if isinstance(obj, type(self)):
139
self.leafs.extend(obj.leafs)
141
self.leafs.append(obj)
142
elif isinstance(obj, list):
145
raise TypeError("%s.add: %r" % (type(self).__name__, type(obj)))
147
def request_data(self, view_name, parent_branch):
148
""" Called by parent component to request data that
149
is used to fill out parent component named view. Return
150
None if component does not add any data to a parent view.
155
def _get_template(cls, name):
156
if name in cls.templates:
157
return cls.templates[name]
160
for c in cls.__bases__:
161
if issubclass(c, Component):
162
r = c._get_template(name)
168
def _get_template_options(cls, name):
169
if name in cls.template_options:
170
return cls.template_options[name]
173
for c in cls.__bases__:
174
if issubclass(c, Component):
175
r = c._get_template_options(name)
180
def get_view(self, view_name, parents=None, ignore_missing_view=False):
181
""" Return a named view of a component using parents.
183
template = self._get_template(view_name)
185
if ignore_missing_view:
188
print '%s does not provide view %r' % (type(self).__name__, view_name)
193
elif isinstance(parents, Component):
194
parents = Branch(parents)
196
branch = parents + self
200
for n1 in parts_re.findall(template):
206
opts = self._get_template_options(l)
208
part_names.append((r,n))
211
assert l==r==n,`l, r, n`
213
v = branch.get_attr(n)
214
except AttributeError, msg:
216
print '%s, using value %r' % (msg, n1)
218
part_containers[n] = v
220
if self.request_own_data:
221
leafs = [self] + self.leafs
226
for r, name in part_names:
227
data = leaf.request_data(r, branch)
229
part_containers[name] += data
232
print '%s.get_view:%s does not provide %r' % (type(self).__name__, type(leaf).__name__, name)
234
result = template % part_containers
236
result = killline_re.sub('', result)
241
""" Return final realization of a component.
243
When implementing realize method, use the following template::
245
self.save() # save current internal state
248
self.restore() # restore initial internal state
251
Note that realize methods may change the original states of
252
component trees, therefore it is recommended to use save and
253
restore methods when entering and leaving the realize method.
256
result = self.get_view('string')
261
""" Save current internal state.
263
self._nof_saved_leafs.append(len(self.leafs))
264
[c.save() for c in self.leafs]
267
""" Restore previous internal state.
269
if not self._nof_saved_leafs:
272
n = self._nof_saved_leafs.pop()
273
while len(self.leafs) > n:
275
[c.restore() for c in self.leafs]
277
def find_component_with_view(self, view_name, view_result):
278
old = self.request_own_data
279
self.request_own_data = False
280
v = self.get_view(view_name, ignore_missing_view=True)
285
for leaf in self.leafs:
286
r = leaf.find_component_with_view(view_name, view_result)
289
self.request_own_data = old
292
########### Test code follows ############
294
class ExampleModule(Component):
303
template_options = dict(
307
def __init__(self, name, *leafs):
308
Component.__init__(self, *leafs)
311
class ExampleFunction(Component):
315
# module name: %(ExampleModule.name)s
316
def %(name)s(%(arguments)s):
321
template_options = dict(
322
arguments = dict(separator=', '),
323
code = dict(default='pass')
326
def __init__(self, name, *leafs):
327
Component.__init__(self, *leafs)
330
def request_data(self, view_name, parents):
331
if view_name=='functions':
332
return self.get_view('source', parents)
335
return self.get_view('source')
337
class ExampleArgument(Component):
341
def __init__(self, name, *leafs):
342
Component.__init__(self, *leafs)
345
def request_data(self, view_name, parents):
346
if view_name=='arguments':
349
class ExampleCode(Component):
354
a = ExampleArgument('a')
355
b = ExampleArgument('b')
356
f = ExampleFunction('foo', a, b)
357
assert f.get_view('source')=='# module name: %(ExampleModule.name)s\ndef foo(a, b):\n pass\n'
358
m = ExampleModule('m', f)
359
assert m.get_view('source')=='# module: m\n# module name: m\ndef foo(a, b):\n pass\n\n'
361
if __name__ == '__main__':