changeset 129:423266f0b9e4

Support inline lists, dicts, etc. And alternative top level objects
author Stefano Rivera <stefano@rivera.za.net>
date Mon, 02 Sep 2013 16:57:59 +0200
parents fbb073720bac
children 67f18e72024d
files nagslang/tests/test_yamlish.py nagslang/yamlish.py
diffstat 2 files changed, 52 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/nagslang/tests/test_yamlish.py	Mon Sep 02 17:23:13 2013 +0200
+++ b/nagslang/tests/test_yamlish.py	Mon Sep 02 16:57:59 2013 +0200
@@ -48,6 +48,9 @@
     def test_simple_dict(self):
         self.roundtrip({'foo': 'bar'})
 
+    def test_simple_list(self):
+        self.roundtrip(['foo', 'bar'])
+
     def test_dict_of_dicts(self):
         self.roundtrip({'foo': {'bar': 'baz'}})
 
@@ -61,11 +64,20 @@
             }
         })
 
+    def test_list_of_lists(self):
+        self.roundtrip(['foo', ['bar', 'baz'], 'qux'])
+
     def test_dict_list(self):
         self.roundtrip({
             'foo': ['bar', 'baz'],
         })
 
+    def test_list_dict(self):
+        self.roundtrip([
+            {'foo': 'bar'},
+            {'baz': 'qux', 'quux': 'corge'},
+        ])
+
     def test_nested_lists(self):
         self.roundtrip({
             'foo': [['bar', 'baz', 'qux'], 'quux'],
@@ -97,6 +109,13 @@
         return yaml.dump(data, default_flow_style=False)
 
 
+class TestFromPyYAMLInlineLists(TestRoundTrip):
+    def dump_s(self, data):
+        if yaml is None:
+            raise SkipTest('yaml module unavailable')
+        return yaml.dump(data)
+
+
 class TestToPyYAML(TestRoundTrip):
     def load_s(self, text):
         if yaml is None:
--- a/nagslang/yamlish.py	Mon Sep 02 17:23:13 2013 +0200
+++ b/nagslang/yamlish.py	Mon Sep 02 16:57:59 2013 +0200
@@ -1,6 +1,6 @@
 '''
 Serializer and dumper for a simple, YAMLish format (actually a YAML subset).
-The top level object is a dict.
+The top level object is a dict or list.
 lists and dicts can contain:
  * lists, dicts,
  * single line strings,
@@ -86,11 +86,12 @@
 class Parser(object):
     _spaces_re = re.compile(r'^(\s*)(.*)')
     _list_re = re.compile(r'^(-\s+)(.*)')
-    _dict_re = re.compile(r'^([^-:]+):\s?(.*)')
+    _dict_re = re.compile(r'^((?![{[])[^-:]+):\s?(.*)')
+    _inline_list_re = re.compile(r"^([^',]+|''|'.+?[^'](?:'')*')(?:, (.*))?$")
 
     def __init__(self):
         # Stack of (indent level, container object)
-        self._stack = [(0, {})]
+        self._stack = []
         # When a dict's value is a nested block, remember the key
         self._parent_key = None
 
@@ -124,6 +125,14 @@
             self._parent_key = None
 
     def parse(self, yaml):
+        if yaml.startswith(('[', '{')):
+            return self._parse_value(yaml)
+
+        if yaml.startswith('-'):
+            self._stack.append((0, []))
+        else:
+            self._stack.append((0, {}))
+
         for line in yaml.splitlines():
             spaces, line = self._spaces_re.match(line).groups()
 
@@ -180,6 +189,27 @@
     def _parse_value(self, value):
         if value.startswith("'") and value.endswith("'"):
             return value[1:-1]
+        if value.startswith('[') and value.endswith(']'):
+            value = value[1:-1]
+            output = []
+            while value:
+                m = self._inline_list_re.match(value)
+                assert m
+                output.append(self._parse_value(m.group(1)))
+                value = m.group(2)
+            return output
+        if value.startswith('{') and value.endswith('}'):
+            value = value[1:-1]
+            output = {}
+            while value:
+                key, value = value.split(': ', 1)
+                m = self._inline_list_re.match(value)
+                assert m
+                output[key] = self._parse_value(m.group(1))
+                value = m.group(2)
+            return output
+        if value.startswith('!!'):
+            raise NotImplemented()
         if value == 'true':
             return True
         if value == 'false':