BulletML.FromDocument: Type detector. Various setstate bug fixes.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Fri, 19 Mar 2010 05:32:51 +0000 (22:32 -0700)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Fri, 19 Mar 2010 05:32:51 +0000 (22:32 -0700)
bulletml-runner
bulletml-to-bulletyaml
bulletml/__init__.py
bulletml/bulletyaml.py
bulletml/expr.py
bulletml/parser.py

index 62e9fdfe4e29918ffbb30c0bb744d119faa835dd..8bfd338c3d2f7fda12a78d5fb71a9de39f2612f2 100755 (executable)
@@ -7,6 +7,12 @@ import time
 import pygame
 
 import bulletml
+import bulletml.bulletyaml
+
+try:
+    import yaml
+except ImportError:
+    yaml = None
 
 try:
     import psyco
index c5f8c561e288284edd09ee3e8998eb7e40e32cff..ebe0e75c8ad0e12d2fa18ed451b316a5998ae8d5 100755 (executable)
@@ -7,7 +7,8 @@ from bulletml import BulletML, bulletyaml
 
 def main(argv):
     for filename in argv:
-        print yaml.dump(BulletML.FromDocument(open(filename, "rU")))
+        print "# Loading", filename
+        print yaml.dump(yaml.load(yaml.dump(BulletML.FromDocument(open(filename, "rU")))))
 
 if __name__ == "__main__":
     main(sys.argv[1:])
index c87a4425b43ac2889828c0695b97ea6a490579a8..addc0fee1bce133a4c49fdd890b9bb9a6ecfbb63 100644 (file)
@@ -6,13 +6,13 @@ Gigawing2, G DARIUS, XEVIOUS, ...) This module parses and executes
 BulletML scripts in Python. All data structures in it are
 renderer-agnostic.
 
+In addition to the standard BulletML XML format, this module supports
+an equivalent YAML format. See bulletml.bulletyaml for more details.
+
 More information is available at the BulletML homepage,
 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html, or the
 python-bullet homepage, http://code.google.com/p/python-bulletml/.
 
-If you want to use a YAML-based implementation, check out the
-bulletml.bulletyaml module in this package.
-
 Basic Usage:
 
     from bulletml import Bullet, BulletML
index ea45436d2e47482a199d110168a80c57d90ee3d4..40949bb0ae393d21c4c7b27673be35e6c82de45f 100644 (file)
@@ -33,13 +33,16 @@ def register(Loader=None, Dumper=None):
                 parser.BulletML]:
 
         def add(cls, loader, dumper):
+            """Register a class in a new variable scope."""
             tag = "!" + cls.__name__
             if loader:
                 def construct(loader, node):
+                    """Construct an object."""
                     return loader.construct_yaml_object(node, cls)
                 loader.add_constructor(tag, construct)
             if dumper:
                 def represent(dumper, obj):
+                    """Represent an object."""
                     return dumper.represent_yaml_object(tag, obj, cls)
                 dumper.add_representer(cls, represent)
 
index b72fa224f7ed49bb2f166582f0ee85176852e003..a00c99eefa4c27f1b9f14d9751d5784f491e5135 100644 (file)
@@ -37,7 +37,7 @@ class NumberDef(object):
             expr = expr.string
         except AttributeError:
             pass
-        self.string = str(expr)
+        self.string = expr = str(expr)
         repl = lambda match: "params[%d]" % (int(match.group()[1:]) - 1)
         expr = re.sub(r"\$\d+", repl, expr.lower())
         self.__expr = expr.replace("$rand", "random()").replace("$rank", "rank")
index a89d1c5b8ce207c7f8a5a37db780f0d38ba43d7f..10b789ae83a801bc0dc538afc183f8194240f00c 100644 (file)
@@ -24,6 +24,7 @@ except ImportError:
 from bulletml.errors import Error
 from bulletml.expr import NumberDef, INumberDef
 
+
 __all_ = ["ParseError", "BulletML"]
 
 class ParseError(Error):
@@ -49,15 +50,6 @@ class ParamList(object):
         return cls([NumberDef(subelem.text) for subelem in element
                     if realtag(subelem) == "param"])
 
-    def __getstate__(self):
-        if self.params:
-            return dict(params=[param.expr for param in self.params])
-        else:
-            return {}
-
-    def __setstate__(self, state):
-        self.__init__(NumberDef(param) for param in state.get("params", []))
-
     def __call__(self, params, rank):
         return [param(params, rank) for param in self.params]
 
@@ -321,7 +313,7 @@ class Accel(object):
         return state
 
     def __setstate__(self, state):
-        self.__init__(INumberDef(state["term"]), state.get("horizontal"),
+        self.__init__(INumberDef(state["frames"]), state.get("horizontal"),
                       state.get("vertical"))
 
     @classmethod
@@ -419,13 +411,13 @@ class BulletRef(object):
     def __getstate__(self):
         state = dict(bullet=self.bullet)
         if self.params.params:
-            state["params"] = self.params.__getstate__()
+            state["params"] = [param.expr for param in self.params.params]
         return state
 
     def __setstate__(self, state):
         bullet = state["bullet"]
         params = [NumberDef(param) for param in state.get("params", [])]
-        self.__init__(bullet, params)
+        self.__init__(bullet, ParamList(params))
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -460,7 +452,7 @@ class ActionDef(object):
         return dict(actions=self.actions)
 
     def __setstate__(self, state):
-        self.__init__(state)
+        self.__init__(state["actions"])
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -494,13 +486,13 @@ class ActionRef(object):
     def __getstate__(self):
         state = dict(action=self.action)
         if self.params.params:
-            state["params"] = self.params.__getstate__()
+            state["params"] = [param.expr for param in self.params.params]
         return state
 
     def __setstate__(self, state):
         action = state["action"]
         params = [NumberDef(param) for param in state.get("params", [])]
-        self.__init__(action, params)
+        self.__init__(action, ParamList(params))
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -584,6 +576,9 @@ class FireDef(object):
             state["offset"] = self.offset
         return state
 
+    def __setstate__(self, state):
+        self.__init__(**state)
+
     @classmethod
     def FromElement(cls, doc, element):
         """Construct using an ElementTree-style element."""
@@ -633,13 +628,13 @@ class FireRef(object):
     def __getstate__(self):
         state = dict(fire=self.fire)
         if self.params.params:
-            state["params"] = self.params.__getstate__()
+            state["params"] = [param.expr for param in self.params.params]
         return state
 
     def __setstate__(self, state):
         fire = state["fire"]
         params = [NumberDef(param) for param in state.get("params", [])]
-        self.__init__(fire, params)
+        self.__init__(fire, ParamList(params))
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -684,11 +679,11 @@ class BulletML(object):
         return dict(type=self.type, actions=self.actions)
 
     def __setstate__(self, state):
-        self.__init__(state["type"], state.get("actions"))
+        self.__init__(state["type"], actions=state.get("actions"))
 
     @classmethod
-    def FromDocument(cls, source):
-        """Return a BulletML instance based on a string or file-like."""
+    def FromXML(cls, source):
+        """Return a BulletML instance based on XML."""
         if not hasattr(source, 'read'):
             source = StringIO(source)
 
@@ -726,6 +721,39 @@ class BulletML(object):
 
         return self
 
+    @classmethod
+    def FromYAML(cls, source):
+        """Create a BulletML instance based on YAML."""
+
+        # Late import to avoid a circular dependency.
+        try:
+            import bulletml.bulletyaml
+            import yaml
+        except ImportError:
+            raise ParseError("PyYAML is not available")
+        else:
+            try:
+                return yaml.load(source)
+            except Exception, exc:
+                raise ParseError(str(exc))
+
+    @classmethod
+    def FromDocument(cls, source):
+        """Create a BulletML instance based on a seekable file or string.
+
+        This attempts to autodetect if the stream is XML or YAML.
+        """
+        if not hasattr(source, 'read'):
+            source = StringIO(source)
+        start = source.read(1)
+        source.seek(0)
+        if start == "<":
+            return cls.FromXML(source)
+        elif start == "!":
+            return cls.FromYAML(source)
+        else:
+            raise ParseError("unknown initial character %r" % start)
+
     @property
     def top(self):
         """Get a list of all top-level actions."""