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