Commits

Leonard Ritter committed d250fa6

* datenwerk schema: new python code generator

Comments (0)

Files changed (6)

python/datenwerk/schema/__init__.py

 
 from datenwerk.schema.parser import parse
 from datenwerk.schema.cpp import write_cpp, write_hpp
+from datenwerk.schema.python import write_python
 
 __all__ = [
     'parse'

python/datenwerk/schema/python.py

+# Copyright (C) 2011 by Leonard Ritter <contact@leonard-ritter.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import time
+import string
+
+from datenwerk.schema.utils import relpathto, SubstMap, IndentWriter
+
+################################################################################
+
+PYTHON_HEADER = """# generated by datenwerk.schema.python, ${CTIME}
+import datenwerk as dw
+import datenwerk.templates as impl
+"""
+
+################################################################################
+
+class Accessor(object):
+    accessors = {} 
+    
+    def __init__(self, cls, name, decl):
+        self.cls = cls
+        self.name = name
+        self.decl = decl
+        
+        self.k = SubstMap(
+            CLS_NAME = self.cls.name,
+            DECL_NAME = self.decl.name,
+            NAME = self.name,
+        )
+
+    @classmethod
+    def add(cls, sub_cls):
+        cls.accessors[sub_cls.__typename__] = sub_cls
+        return sub_cls
+
+    @classmethod
+    def get(cls, cls_decl, name, decl):
+        return cls.accessors[decl.type](cls_decl, name, decl)
+    
+    def __call__(self, *args, **kargs):
+        return self.k.__call__(*args, **kargs)
+
+    def write_get_item_impl(self, writer):
+        writer.write(self('def ${NAME}_item(self):\n'))
+        writer.inc()
+        writer.write(self('return impl.default_key($DWK_CLS, self, "$NAME", $DEFAULT)\n'))
+        writer.dec(); writer.write('\n')
+
+    def write_clear_impl(self, writer):
+        writer.write(self('def clear_$NAME(self):\n'))
+        writer.inc()
+        writer.write(self('impl.clear(self, "$NAME")\n'))
+        writer.dec(); writer.write('\n')
+
+    def write_has_impl(self, writer):
+        writer.write(self('def has_$NAME(self):\n'))
+        writer.inc()
+        writer.write(self('return impl.has_key($DWK_CLS, self, "$NAME")\n'))
+        writer.dec(); writer.write('\n')
+
+    def write_decl(self, writer):
+        pass
+    
+    def write_impl(self, writer):
+        pass
+    
+    def write_property(self, writer):
+        writer.write(self('$NAME = property(fget = get_$NAME, fset = set_$NAME)\n'))
+
+@Accessor.add
+class ObjectAccessor(Accessor):
+    __typename__ = 'object'
+
+    def __init__(self, *args, **kargs):
+        Accessor.__init__(self, *args, **kargs)
+        self.k.update(dict(
+            DWK_CLS = 'dw.Object',
+            DEFAULT = 'None',
+        ))
+
+    def write_impl(self, writer):
+        self.write_has_impl(writer)
+        self.write_clear_impl(writer)
+        writer.write(self('def get_$NAME(self):\n')) 
+        writer.inc()
+        writer.write(self('return $DECL_NAME.cast(impl.get_key($DWK_CLS, self, "$NAME"))\n'))
+        writer.dec(); writer.write('\n')
+        
+        writer.write(self('def set_$NAME(self, value):\n')) 
+        writer.inc()
+        writer.write(self('impl.set_value($DWK_CLS, self, "$NAME", value)\n'))
+        writer.dec(); writer.write('\n')
+        
+        self.write_property(writer)
+
+@Accessor.add
+class ArrayAccessor(Accessor):
+    __typename__ = 'array'
+
+    def __init__(self, *args, **kargs):
+        Accessor.__init__(self, *args, **kargs)
+        item_acc = Accessor.get(self.cls, self.name, self.decl.items) 
+        self.k.update(dict(
+            DWK_CLS = 'dw.Array',
+            ITEM_CLS = item_acc('$DWK_CLS'),
+            ITEM_CLS_NAME = item_acc('$DECL_NAME'),
+            DEFAULT = item_acc('$DEFAULT'),
+        ))
+
+    def write_impl(self, writer):
+        self.write_clear_impl(writer)
+        writer.write(self('def ${NAME}_size(self):\n'))
+        writer.inc()
+        writer.write(self('return impl.get_size(self, "$NAME")\n'))
+        writer.dec(); writer.write('\n')
+        
+        writer.write(self('def $NAME(self, index):\n'))
+        writer.inc()
+        writer.write(self('return $ITEM_CLS_NAME.cast(impl.get_element_value($ITEM_CLS, self, "$NAME", index, $DEFAULT))\n'))
+        writer.dec(); writer.write('\n')
+        
+        writer.write(self('def add_$NAME(self, value):\n'))
+        writer.inc()
+        writer.write(self('impl.add_element($ITEM_CLS, self, "$NAME", value)\n'))
+        writer.dec(); writer.write('\n')
+        
+        self.write_get_item_impl(writer)
+
+@Accessor.add
+class LinkAccessor(Accessor):
+    __typename__ = 'link'
+
+    def __init__(self, *args, **kargs):
+        Accessor.__init__(self, *args, **kargs)
+        item_acc = Accessor.get(self.cls, self.name, self.decl.items) 
+        self.k.update(dict(
+            DWK_CLS = 'dw.Link',
+            ITEM_CLS_NAME = item_acc('$DECL_NAME'),
+            DEFAULT = 'None',
+        ))
+    
+    def write_impl(self, writer):
+        self.write_has_impl(writer)
+        self.write_clear_impl(writer)
+        
+        writer.write(self('def get_$NAME(self):\n')) 
+        writer.inc()
+        writer.write(self('return $ITEM_CLS_NAME.cast(impl.get_value($DWK_CLS, self, "$NAME", $DEFAULT))\n'))
+        writer.dec(); writer.write('\n')
+        
+        writer.write(self('def set_$NAME(self, value):\n')) 
+        writer.inc()
+        writer.write(self('impl.set_value($DWK_CLS, self, "$NAME", value)\n'))
+        writer.dec(); writer.write('\n')
+        
+        self.write_get_item_impl(writer)
+        self.write_property(writer)
+
+class ValueAccessor(Accessor):
+    def __init__(self, *args, **kargs):
+        Accessor.__init__(self, *args, **kargs)
+    
+    def write_impl(self, writer):
+        self.write_has_impl(writer)
+        self.write_clear_impl(writer)
+        
+        writer.write(self('def get_$NAME(self):\n')) 
+        writer.inc()
+        writer.write(self('return impl.get_value($DWK_CLS, self, "$NAME", $DEFAULT)\n'))
+        writer.dec(); writer.write('\n')
+        
+        writer.write(self('def set_$NAME(self, value):\n')) 
+        writer.inc()
+        writer.write(self('impl.set_value($DWK_CLS, self, "$NAME", value)\n'))
+        writer.dec(); writer.write('\n')
+        
+        self.write_get_item_impl(writer)
+        self.write_property(writer)
+
+@Accessor.add
+class IntegerAccessor(ValueAccessor):
+    __typename__ = 'integer'
+
+    def __init__(self, *args, **kargs):
+        ValueAccessor.__init__(self, *args, **kargs)
+        self.k.update(dict(
+            DWK_CLS = 'dw.Int',
+            DEFAULT = '0',
+        ))
+
+@Accessor.add
+class BooleanAccessor(ValueAccessor):
+    __typename__ = 'boolean'
+
+    def __init__(self, *args, **kargs):
+        ValueAccessor.__init__(self, *args, **kargs)
+        self.k.update(dict(
+            DWK_CLS = 'dw.Bool',
+            DEFAULT = 'False',
+        ))
+
+@Accessor.add
+class NumberAccessor(ValueAccessor):
+    __typename__ = 'number'
+
+    def __init__(self, *args, **kargs):
+        ValueAccessor.__init__(self, *args, **kargs)
+        self.k.update(dict(
+            DWK_CLS = 'dw.Float',
+            DEFAULT = '0.0',
+        ))
+
+@Accessor.add
+class StringAccessor(ValueAccessor):
+    __typename__ = 'string'
+
+    def __init__(self, *args, **kargs):
+        ValueAccessor.__init__(self, *args, **kargs)
+        self.k.update(dict(
+            DWK_CLS = 'dw.String',
+            DEFAULT = '""',
+        ))
+
+def write_class_impl(decl, writer):
+    k = SubstMap(
+        NAME = decl.name,
+    )
+    writer.write('#' * 80 + '\n\n')
+    # constructor
+    writer.write('@impl.make_schema_item\n')
+    writer.write(k('class $NAME(impl.SchemaItem):\n'))
+    writer.inc()
+
+    # write accessor implementations
+    for name,prop in decl.properties.iteritems():
+        writer.write('# ' + name + '\n')
+        accessor = Accessor.get(decl, name, prop)
+        accessor.write_impl(writer)
+        writer.write('\n')
+    writer.dec()
+    writer.write('\n')
+
+def write_python(schema, fobj):
+    k = SubstMap(
+        CTIME = time.ctime()
+    )
+    writer = IndentWriter(fobj)
+    writer.write(k(PYTHON_HEADER))
+    # ns = NamespaceWriter(writer)
+    for decl,path,deps in schema.walk():
+        if decl.type != 'object':
+            continue
+        # ns.write(decl.namespace)
+        write_class_impl(decl, writer)
+    # ns.write([])

python/datenwerk/templates.py

+
+import inspect
+import datenwerk as dw
+
+types = {
+}
+
+def add_type(cls):
+    types[cls.__dw_cls__] = cls
+    return cls
+
+class Accessor(object):
+    @staticmethod
+    def extract(item, defvalue):
+        if item is None:
+            return defvalue
+        return item
+
+@add_type
+class ObjectAccessor(Accessor):
+    __dw_cls__ = dw.Object
+
+    @classmethod
+    def wrap(cls, value):
+        return dw.get_item(value)
+
+@add_type
+class ArrayAccessor(Accessor):
+    __dw_cls__ = dw.Array
+
+    @classmethod
+    def wrap(cls, value):
+        return dw.get_item(value)
+
+class ValueAccessor(Accessor):
+    @classmethod
+    def wrap(cls, value):
+        item = cls.__dw_cls__()
+        item.set(value)
+        return item
+    
+    @staticmethod
+    def extract(item, defvalue):
+        if item is None:
+            return defvalue
+        return item.get()
+    
+    @classmethod
+    def set(cls, item, key, value):
+        property = get_key(cls, item, key)
+        if not property:
+            clear(item, key)
+            item.set_key(key, cls.wrap(value))
+        else:
+            property.set(value)
+
+@add_type
+class LinkAccessor(ValueAccessor):
+    __dw_cls__ = dw.Link
+
+    @classmethod
+    def wrap(cls, value):
+        item = cls.__dw_cls__()
+        item.set(dw.get_item(value))
+        return item
+    
+    @staticmethod
+    def extract(item, defvalue):
+        if item is None:
+            return defvalue
+        return item.get()
+    
+    @classmethod
+    def set(cls, item, key, value):
+        property = get_key(cls, item, key)
+        if not property:
+            clear(item, key)
+            item.set_key(key, cls.wrap(value))
+        else:
+            property.set(dw.get_item(value))
+
+@add_type
+class BoolAccessor(ValueAccessor):
+    __dw_cls__ = dw.Bool
+
+@add_type
+class IntAccessor(ValueAccessor):
+    __dw_cls__ = dw.Int
+
+@add_type
+class StringAccessor(ValueAccessor):
+    __dw_cls__ = dw.String
+
+@add_type
+class FloatAccessor(ValueAccessor):
+    __dw_cls__ = dw.Float
+
+def get_key(cls, item, key):
+    item = dw.get_item(item)
+    value = item.get_key(key)
+    if value:
+        if isinstance(value, cls):
+            return value
+    return None
+
+def has_key(cls, item, key):
+    item = dw.get_item(item)
+    value = item.get_key(key)
+    if value:
+        if isinstance(value, cls):
+            return True
+    return False
+
+def clear(item, key):
+    item = dw.get_item(item)
+    if item.has_key(key):
+        item.del_key(key)
+
+def get_value(cls, item, key, defvalue):
+    item = dw.get_item(item)
+    return types[cls].extract(get_key(cls, item, key), defvalue)
+
+def set_value(cls, item, key, value):
+    item = dw.get_item(item)
+    return types[cls].set(item, key, value)  
+
+def get_element(cls, item, key, index):
+    item = dw.get_item(item)
+    array = get_key(dw.Array, item, key)
+    if array and (index < array.count()):
+        value = array.get(index)
+        if isinstance(value, cls):
+            return value
+    return None
+
+def get_element_value(cls, item, key, index, defvalue):
+    item = dw.get_item(item)
+    return types[cls].extract(get_element(cls, item, key, index), defvalue) 
+
+def get_size(item, key):
+    item = dw.get_item(item)
+    array = get_key(dw.Array, item, key)
+    if array is None:
+        return 0
+    return array.count()
+
+def add_element(cls, item, key, value):
+    item = dw.get_item(item)
+    array = get_key(dw.Array, item, key)
+    element = types[cls].wrap(value)
+    if array is None:
+        clear(item, key)
+        array = dw.Array()
+        array.append(element)
+        item.set_key(key, array)
+    else:
+        array.append(element)
+
+_wrapped_classes = set()
+
+class SchemaItem(object):
+    def __new__(cls, *args, **kargs):
+        try:
+            item = kargs.pop('__item__')
+        except KeyError:
+            item = None
+        if not item:
+            item = dw.Object() #cls.__template__.new_item()
+        instance = object.__new__(cls)
+        instance.__item = item
+        # add instance as entry for the class and all baseclasses
+        for base_class in inspect.getmro(cls):
+            # stop at SchemaItem base class
+            if base_class == SchemaItem:
+                break
+            if not (base_class in _wrapped_classes):
+                continue
+            item.__dict__[base_class] = instance
+        return instance
+    
+    def __dw_item__(self):
+        return self.__item
+
+def make_schema_item(cls):
+    assert issubclass(cls, SchemaItem), '%r must be a subclass of SchemaItem' % cls
+    
+    if cls in _wrapped_classes:
+        return
+    
+    _wrapped_classes.add(cls)
+    
+    def cast(cls, item):
+        if item is None:
+            return None
+        item = dw.get_item(item)
+        instance = item.__dict__.get(cls,None)
+        if not instance:
+            if hasattr(cls, '__upcast__'):
+                cls = cls.__upcast__(item)
+            instance = SchemaItem.__new__(cls, __item__ = item)
+            instance.__init__()
+        return instance
+    
+    classname = "%s.%s" % (cls.__module__, cls.__name__)
+    
+    def __repr__(self):
+        return '<%s(%r)>' % (classname, self.__dw_item__())
+    
+    setattr(cls, 'cast', classmethod(cast))
+    setattr(cls, '__repr__', __repr__)
+    
+    return cls

python/utils/dwsc

 from StringIO import StringIO
 
 import datenwerk as dw
-from datenwerk.schema import parse, write_cpp, write_hpp 
+from datenwerk.schema import parse, write_cpp, write_hpp, write_python
 
 if __name__ == "__main__":
     parser = OptionParser()
     parser.add_option("--cpp-out", dest="cpp_out",
                       help="target folder to write to", metavar="folder")
+    parser.add_option("--python-out", dest="python_out",
+                      help="target folder to write to", metavar="folder")
     options, args = parser.parse_args()
     
     if not args:
             print "Writing %s..." % hpp_path
             file(hpp_path, "w").write(hpp_data.getvalue())
             print "Done."
+        if options.python_out:
+            py_path = os.path.join(options.python_out, basename + '_dw.py')
+            py_data = StringIO()
+            write_python(schema, py_data)
+            print "Writing %s..." % py_path
+            file(py_path, "w").write(py_data.getvalue())
+            print "Done."

tests/schemapp/SConscript

         basename = os.path.splitext(fname.name)[0]
         target.append(basename + '.dw.cpp')
         target.append(basename + '.dw.hpp')
+        target.append(basename + '_dw.py')
     return target, source
 
 def compile_schema(target, source, env):
     cpp_out_folder = os.path.dirname(target[0].path)
+    py_out_folder = os.path.dirname(target[0].path)
     source_paths = ' '.join([f.path for f in source])
-    cmdline = 'dwsc --cpp-out=' + cpp_out_folder + ' ' + source_paths
+    cmdline = ' '.join(['dwsc',
+        '--cpp-out=' + cpp_out_folder,
+        '--python-out=' + py_out_folder,
+        source_paths])
     print cmdline
     return os.system(cmdline) 
 

tests/schemapp/test.py

+
+import sys
+import os
+basepath = os.path.dirname(__file__) 
+for path in [
+        '../../build/linux-x86',
+        '../../build/linux-x86_64',
+        '../../build/darwin-x86',
+        '../../build/darwin-x86_64',
+    ]:
+    sys.path.insert(0, os.path.join(basepath, path, 'debug/tests/schemapp'))
+    sys.path.insert(0, os.path.join(basepath, path, 'release/tests/schemapp'))
+
+from test_dw import *
+
+def main():
+    print "*** doc"
+
+    doc = Document()
+
+    print "*** int_value"
+
+    assert doc.int_value == 0
+    assert not doc.has_int_value()
+    doc.int_value = 23
+    assert doc.has_int_value()
+    assert doc.int_value == 23
+    doc.clear_int_value()
+    assert doc.int_value == 0
+    assert not doc.has_int_value()
+
+    print "*** bool_value"
+
+    assert doc.boolean_value == False
+    assert not doc.has_boolean_value()
+    doc.boolean_value = True
+    assert doc.has_boolean_value()
+    assert doc.boolean_value == True
+    doc.clear_boolean_value()
+    assert doc.boolean_value == False
+    assert not doc.has_boolean_value()
+
+    print "*** number_value"
+
+    assert doc.number_value == 0.0
+    assert not doc.has_number_value()
+    doc.number_value = 3.14
+    assert doc.has_number_value()
+    assert doc.number_value == 3.14
+    doc.clear_number_value()
+    assert doc.number_value == 0.0
+    assert not doc.has_number_value()
+
+    print "*** string_value"
+
+    assert doc.string_value == ""
+    assert not doc.has_string_value()
+    doc.string_value = "Johnny Cash"
+    assert doc.has_string_value()
+    assert doc.string_value == "Johnny Cash"
+    doc.clear_string_value()
+    assert doc.string_value == ""
+    assert not doc.has_string_value()
+
+    print "*** link_value"
+
+    assert doc.link_value == None
+    assert not doc.has_link_value()
+    testobj = Object()
+    doc.link_value = testobj
+    assert doc.has_link_value()
+    assert doc.link_value == testobj
+    doc.clear_link_value()
+    assert doc.link_value == None
+    assert not doc.has_link_value()
+
+    print "*** object_array_value"
+
+    assert not doc.object_array_value_size()
+    o1 = Object()
+    o1.int_value = 23
+    doc.add_object_array_value(o1)
+    assert doc.object_array_value_size() == 1
+    assert doc.object_array_value(0).int_value == 23
+    doc.add_object_array_value(Object())
+    assert doc.object_array_value_size() == 2
+    doc.add_object_array_value(Object())
+    assert doc.object_array_value_size() == 3
+    doc.clear_object_array_value()
+    assert not doc.object_array_value_size()
+
+    print "*** ok"
+
+if __name__ == '__main__':
+    main()