~pearu-peterson/f2py/trunk

« back to all changes in this revision

Viewing changes to srcgen/component.py

  • Committer: Pearu Peterson
  • Date: 2008-05-04 20:43:42 UTC
  • Revision ID: pearu.peterson@gmail.com-20080504204342-i0zr7iaufdp2n2ej
Introducing srcgen package (a rewrite of extgen, more robust, cleaner).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
__all__ = ['Component']
 
3
 
 
4
import re
 
5
from joiner import Joiner
 
6
 
 
7
DEBUG = True
 
8
parts_re = re.compile(r'%\([\w.]+\)[rs]')
 
9
killline_re = re.compile(r'.*[<]KILLLINE[>].*(\n|$)')
 
10
 
 
11
class Branch(object):
 
12
    """ Represents a branch of a Component tree.
 
13
    """
 
14
    def __init__(self, *items):
 
15
        self.items = items
 
16
 
 
17
    def __add__(self, other):
 
18
        if isinstance(other, Component):
 
19
            return type(self)(*(self.items + (other,)))
 
20
        return NotImplemented
 
21
 
 
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.
 
28
        """
 
29
        if '.' in name:
 
30
            classname, attrname = name.split('.')
 
31
        else:
 
32
            classname, attrname = None, name
 
33
        for c in reversed(self.items):
 
34
            if classname and classname!=type(c).__name__:
 
35
                continue
 
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))
 
41
        return default
 
42
 
 
43
    def get_left(self, component):
 
44
        if not self.items:
 
45
            return []
 
46
        parent = self.items[-1]
 
47
        r = []
 
48
        for leaf in parent.leafs:
 
49
            if leaf==component:
 
50
                return r
 
51
            r.append(leaf)
 
52
        return r
 
53
 
 
54
    def find_component_with_view(self, view_name, view_result):
 
55
        if not self.items:
 
56
            return
 
57
        return self.items[0].find_component_with_view(view_name, view_result)
 
58
        
 
59
class Component(object):
 
60
    """ Base class for Component subclasses.
 
61
 
 
62
    This class implements a certain combination of Composite
 
63
    and Template method patterns.
 
64
 
 
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
 
68
    subclass.
 
69
 
 
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:
 
73
 
 
74
    #. the name of a view template part for which data is requested,
 
75
       (leaves must know how to serve their parents).
 
76
 
 
77
    #. a parent branch that provides parent data as well as the
 
78
       possibility to access the data of neighboring leaves.
 
79
 
 
80
    When the returned values from the component leafs are collected,
 
81
    then the templates will be realized.
 
82
 
 
83
    Implementation notes:
 
84
 
 
85
    #. data requests are done with ``request_data(view_name,
 
86
       parents)`` method where ``parents`` argument contains a parent branch.
 
87
 
 
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.
 
91
 
 
92
    #. the results of component data requestes are collected
 
93
       in ``Joiner`` instances (see joiner.py).
 
94
 
 
95
    #. the view is obtained by calling ``get_view(view_name, parents=Branch())``::
 
96
 
 
97
         <view template> % dict(<name of view template part> = <Joiner instance>)
 
98
 
 
99
    #. realization of a template is returned by the ``realize()`` method.
 
100
 
 
101
    #. component classes can redefine the following methods:
 
102
 
 
103
         #. ``__init__`` --- to define component specific data
 
104
            attributes
 
105
 
 
106
         #. ``request_data`` --- to connect leafs with parents
 
107
 
 
108
         #. ``realize`` --- to return the final realization of a
 
109
            component, it can be anything, including executing
 
110
            external commands
 
111
 
 
112
    """
 
113
 
 
114
    # templates dictionary contains mappings:
 
115
    #   {<view name>:<template string>}
 
116
    templates = dict()
 
117
 
 
118
    # template_options dictionary contains mappings:
 
119
    #   {<name of template part>:<options dictionary to Joiner constructor>}
 
120
    template_options = dict()
 
121
 
 
122
    # set True if get_view can call request_data:
 
123
    request_own_data = False
 
124
 
 
125
    def __init__(self, *leafs):
 
126
        self.leafs = []
 
127
        self._nof_saved_leafs = [0]
 
128
        map(self.add, leafs)
 
129
 
 
130
    @classmethod
 
131
    def _check_options(cls, options, *expected_names):
 
132
        unknown_names = set(options).difference(expected_names)
 
133
        if unknown_names:
 
134
            raise ValueError('%s: unknown options %r' % (cls.__name__, unknown_names))
 
135
 
 
136
    def add(self, obj):
 
137
        if isinstance(obj, Component):
 
138
            if isinstance(obj, type(self)):
 
139
                self.leafs.extend(obj.leafs)
 
140
            else:
 
141
                self.leafs.append(obj)
 
142
        elif isinstance(obj, list):
 
143
            map(self.add, obj)
 
144
        else:
 
145
            raise TypeError("%s.add: %r" % (type(self).__name__, type(obj)))
 
146
 
 
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.
 
151
        """
 
152
        return None
 
153
 
 
154
    @classmethod
 
155
    def _get_template(cls, name):
 
156
        if name in cls.templates:
 
157
            return cls.templates[name]
 
158
        if cls is Component:
 
159
            return None
 
160
        for c in cls.__bases__:
 
161
            if issubclass(c, Component):
 
162
                r = c._get_template(name)
 
163
                if r is not None:
 
164
                    return r
 
165
        return None
 
166
 
 
167
    @classmethod
 
168
    def _get_template_options(cls, name):
 
169
        if name in cls.template_options:
 
170
            return cls.template_options[name]
 
171
        if cls is Component:
 
172
            return None
 
173
        for c in cls.__bases__:
 
174
            if issubclass(c, Component):
 
175
                r = c._get_template_options(name)
 
176
                if r is not None:
 
177
                    return r
 
178
        return None
 
179
 
 
180
    def get_view(self, view_name, parents=None, ignore_missing_view=False):
 
181
        """ Return a named view of a component using parents.
 
182
        """
 
183
        template = self._get_template(view_name)
 
184
        if template is None:
 
185
            if ignore_missing_view:
 
186
                return
 
187
            if DEBUG:
 
188
                print '%s does not provide view %r' % (type(self).__name__, view_name)
 
189
            return
 
190
 
 
191
        if parents is None:
 
192
            parents = Branch()
 
193
        elif isinstance(parents, Component):
 
194
            parents = Branch(parents)
 
195
 
 
196
        branch = parents + self
 
197
 
 
198
        part_names = []
 
199
        part_containers = {}
 
200
        for n1 in parts_re.findall(template):
 
201
            n = n1[2:-2]
 
202
            if '..' in n:
 
203
                l, r = n.split('..')
 
204
            else:
 
205
                l = r = n
 
206
            opts = self._get_template_options(l)
 
207
            if opts is not None:
 
208
                part_names.append((r,n))
 
209
                v = Joiner(**opts)
 
210
            else:
 
211
                assert l==r==n,`l, r, n`
 
212
                try:
 
213
                    v = branch.get_attr(n)
 
214
                except AttributeError, msg:
 
215
                    if DEBUG:
 
216
                        print '%s, using value %r' % (msg, n1)
 
217
                    v = n1
 
218
            part_containers[n] = v
 
219
 
 
220
        if self.request_own_data:
 
221
            leafs = [self] + self.leafs
 
222
        else:
 
223
            leafs = self.leafs
 
224
 
 
225
        for leaf in leafs:        
 
226
            for r, name in part_names:
 
227
                data = leaf.request_data(r, branch)
 
228
                if data is not None:
 
229
                    part_containers[name] += data
 
230
                else:
 
231
                    if DEBUG and 0:
 
232
                        print '%s.get_view:%s does not provide %r' % (type(self).__name__, type(leaf).__name__, name)
 
233
 
 
234
        result = template % part_containers
 
235
 
 
236
        result = killline_re.sub('', result)
 
237
 
 
238
        return result
 
239
 
 
240
    def realize(self):
 
241
        """ Return final realization of a component.
 
242
 
 
243
        When implementing realize method, use the following template::
 
244
 
 
245
          self.save()    # save current internal state
 
246
          ...
 
247
          result = ...
 
248
          self.restore() # restore initial internal state
 
249
          return result
 
250
 
 
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.
 
254
        """
 
255
        self.save()
 
256
        result = self.get_view('string')
 
257
        self.restore()
 
258
        return result
 
259
 
 
260
    def save(self):
 
261
        """ Save current internal state.
 
262
        """
 
263
        self._nof_saved_leafs.append(len(self.leafs))
 
264
        [c.save() for c in self.leafs]
 
265
 
 
266
    def restore(self):
 
267
        """ Restore previous internal state.
 
268
        """
 
269
        if not self._nof_saved_leafs:
 
270
            n = 0
 
271
        else:
 
272
            n = self._nof_saved_leafs.pop()
 
273
        while len(self.leafs) > n:
 
274
            self.leafs.pop()
 
275
        [c.restore() for c in self.leafs]
 
276
 
 
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)
 
281
        if v==view_result:
 
282
            r = self
 
283
        else:
 
284
            r = None
 
285
            for leaf in self.leafs:
 
286
                r = leaf.find_component_with_view(view_name, view_result)
 
287
                if r is not None:
 
288
                    break
 
289
        self.request_own_data = old
 
290
        return r
 
291
 
 
292
########### Test code follows ############
 
293
 
 
294
class ExampleModule(Component):
 
295
 
 
296
    templates = dict(
 
297
        source = '''\
 
298
# module: %(name)s
 
299
%(functions)s
 
300
'''
 
301
        )
 
302
 
 
303
    template_options = dict(
 
304
        functions = dict()
 
305
        )
 
306
 
 
307
    def __init__(self, name, *leafs):
 
308
        Component.__init__(self, *leafs)
 
309
        self.name = name
 
310
 
 
311
class ExampleFunction(Component):
 
312
 
 
313
    templates = dict(
 
314
        source = '''\
 
315
# module name: %(ExampleModule.name)s
 
316
def %(name)s(%(arguments)s):
 
317
    %(code)s
 
318
'''
 
319
        )
 
320
 
 
321
    template_options = dict(
 
322
        arguments = dict(separator=', '),
 
323
        code = dict(default='pass')
 
324
    )
 
325
 
 
326
    def __init__(self, name, *leafs):
 
327
        Component.__init__(self, *leafs)
 
328
        self.name = name
 
329
 
 
330
    def request_data(self, view_name, parents):
 
331
        if view_name=='functions':
 
332
            return self.get_view('source', parents)
 
333
 
 
334
    def realize(self):
 
335
        return self.get_view('source')
 
336
 
 
337
class ExampleArgument(Component):
 
338
 
 
339
    templates = dict()
 
340
 
 
341
    def __init__(self, name, *leafs):
 
342
        Component.__init__(self, *leafs)
 
343
        self.name = name
 
344
 
 
345
    def request_data(self, view_name, parents):
 
346
        if view_name=='arguments':
 
347
            return self.name
 
348
 
 
349
class ExampleCode(Component):
 
350
 
 
351
    templates = dict()
 
352
 
 
353
def _test():
 
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'
 
360
 
 
361
if __name__ == '__main__':
 
362
    _test()
 
363
    print 'ok'