diff --git a/.gitignore b/.gitignore index 0848d84..20acb77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.coverage /.python-version /htmlcov +/venv __javascript__ __pycache__ diff --git a/build_js.sh b/build_js.sh index 6376697..841e716 100755 --- a/build_js.sh +++ b/build_js.sh @@ -15,13 +15,19 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -transcrypt -b -n eos.js.py +set +e -# Javascript identifiers cannot contain dots -perl -0777 -pi -e 's/eos.js/eosjs/g' eos/__javascript__/eos.js.js +FLAGS=-k -# __pragma__ sometimes stops working??? -perl -0777 -pi -e "s/__pragma__ \('.*?'\)//gs" eos/__javascript__/eos.js.js - -# Transcrypt by default suppresses stack traces for some reason?? -perl -0777 -pi -e "s/__except0__.__cause__ = null;//g" eos/__javascript__/eos.js.js +for f in eos.js eos.js_tests; do + transcrypt -b -n $FLAGS $f.py + + # Javascript identifiers cannot contain dots + perl -0777 -pi -e 's/eos.js/eosjs/g' eos/__javascript__/$f.js + + # __pragma__ sometimes stops working??? + perl -0777 -pi -e "s/__pragma__ \('.*?'\)//gs" eos/__javascript__/$f.js + + # Transcrypt by default suppresses stack traces for some reason?? + perl -0777 -pi -e "s/__except0__.__cause__ = null;//g" eos/__javascript__/$f.js +done diff --git a/eos/core/objects/js.py b/eos/core/objects/js.py index 05274e1..2c16efa 100644 --- a/eos/core/objects/js.py +++ b/eos/core/objects/js.py @@ -14,45 +14,166 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# Fields +# ====== + class Field: def __init__(self, *args, **kwargs): - if 'default' in kwargs: - self.required = False - self.default = kwargs['default'] - else: - self.required = True - self.default = None + self.default = kwargs.get('default', None) + self.hashed = kwargs.get('hashed', True) -StringField = Field +class PrimitiveField(Field): + def serialise(self, value): + return value + + def deserialise(self, value): + return value + +DictField = PrimitiveField +IntField = PrimitiveField +ListField = PrimitiveField +StringField = PrimitiveField +UUIDField = PrimitiveField # Different to Python + +class EmbeddedObjectField(Field): + def __init__(self, object_type=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.object_type = object_type + + def serialise(self, value): + return EosObject.serialise_and_wrap(value, self.object_type) + + def deserialise(self, value): + return EosObject.deserialise_and_unwrap(value, self.object_type) + +class ListField(Field): + def __init__(self, element_field=None, *args, **kwargs): + super().__init__(default=EosList, *args, **kwargs) + self.element_field = element_field + + def serialise(self, value): + return [self.element_field.serialise(x) for x in value] + + def deserialise(self, value): + return [self.element_field.deserialise(x) for x in value] + +class EmbeddedObjectListField(Field): + def __init__(self, object_type=None, *args, **kwargs): + super().__init__(default=EosList, *args, **kwargs) + self.object_type = object_type + + def serialise(self, value): + return [EosObject.serialise_and_wrap(x, self.object_type) for x in value] + + def deserialise(self, value): + return [EosObject.deserialise_and_unwrap(x, self.object_type) for x in value] + +# Objects +# ======= class EosObjectType(type): - def before_new(meta, name, bases, attrs): - # Process fields - fields = {} - for attr, val in dict(attrs).items(): - if isinstance(val, Field): - fields[attr] = val - attrs[attr] = val.to_python() - attrs['_fields'] = fields - - return meta, name, bases, attrs - def __new__(meta, name, bases, attrs): - meta, name, bases, attrs = meta.before_new(meta, name, bases, attrs) - #return super().__new__(meta, name, bases, attrs) - return type.__new__(meta, name, bases, attrs) + cls = type.__new__(meta, name, bases, attrs) + cls._name = cls.__module__ + '.' + cls.__qualname__ + if name != 'EosObject': + EosObject.objects[cls._name] = cls + return cls -class EosObject(): +class EosObject(metaclass=EosObjectType): + objects = {} + + def __init__(self): + self._instance = (None, None) + self._inited = False + + def post_init(self): + self._inited = True + + def recurse_parents(self, cls): + if isinstance(self, cls): + return self + if self._instance[0]: + return self._instance[0].recurse_parents(cls) + return None + + @staticmethod + def serialise_and_wrap(value, object_type=None): + if object_type: + return value.serialise() + return {'type': value._name, 'value': value.serialise()} + + @staticmethod + def deserialise_and_unwrap(value, object_type=None): + if object_type: + return object_type.deserialise(value) + return EosObject.objects[value['type']].deserialise(value['value']) + +class EosList(EosObject, list): + def append(self, value): + if isinstance(value, EosObject): + value._instance = (self, len(self)) + if not value._inited: + value.post_init() + return super().append(value) + +class DocumentObjectType(EosObjectType): + def __new__(meta, name, bases, attrs): + cls = EosObjectType.__new__(meta, name, bases, attrs) + + # Process fields + fields = Object.create(cls._fields) if hasattr(cls, '_fields') else {} # Different to Python: TNYI dict.copy not implemented + for attr in list(dir(cls)): + val = getattr(cls, attr) + if isinstance(val, Field): + val._instance = (cls, name) + fields[attr] = val + delattr(cls, attr) + cls._fields = fields + + # Make properties + def make_property(name, field): + def field_getter(self): + return self._field_values[name] + def field_setter(self, value): + if isinstance(value, EosObject): + value._instance = (self, name) + if not value._inited: + value.post_init() + + self._field_values[name] = value + return property(field_getter, field_setter) + + for attr, val in fields.items(): + setattr(cls, attr, make_property(attr, val)) + + return cls + +class DocumentObject(EosObject, metaclass=DocumentObjectType): + _ver = StringField(default='0.1') + + def __init__(self, *args, **kwargs): + super().__init__() + + self._field_values = {} + + for attr, val in self._fields.items(): + if attr in kwargs: + setattr(self, attr, kwargs[attr]) + else: + default = val.default + if callable(default): + default = default() + setattr(self, attr, default) + + def serialise(self): + return {attr: val.serialise(getattr(self, attr)) for attr, val in self._fields.items()} + + @classmethod + def deserialise(cls, value): + return cls(**{attr: val.deserialise(value[attr]) for attr, val in cls._fields.items()}) + +class TopLevelObject(DocumentObject): pass -class DocumentObject(EosObject, metaclass=EosObjectType): - def __init__(self, *args, **kwargs): - # Process fields - for name, field in self._fields.items(): - if name not in kwargs: - continue - setattr(self, name, kwargs.pop(name, field.default)) - -# MongoDB distinguishes between these two, but we don't care -TopLevelObject = DocumentObject -EmbeddedObject = DocumentObject +class EmbeddedObject(DocumentObject): + pass diff --git a/eos/js.py b/eos/js.py index 6828340..279f0d6 100644 --- a/eos/js.py +++ b/eos/js.py @@ -16,3 +16,4 @@ import eos.core.objects import eos.core.bigint +import eos.core.bitstring diff --git a/eos/js_tests.py b/eos/js_tests.py new file mode 100644 index 0000000..3187661 --- /dev/null +++ b/eos/js_tests.py @@ -0,0 +1,19 @@ +# Eos - Verifiable elections +# Copyright © 2017 RunasSudo (Yingtong Li) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import eos.js + +import eos.core.test_code diff --git a/js.html b/js.html index 4f07221..93c6491 100644 --- a/js.html +++ b/js.html @@ -1 +1 @@ - + diff --git a/requirements.txt b/requirements.txt index 675e387..18f13f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +coverage==4.4.1 mypy==0.521 pymongo==3.5.1 Transcrypt==3.6.50