source: nagslang/yamlish.py@ 111:16d9f3a0dc29

Last change on this file since 111:16d9f3a0dc29 was 111:16d9f3a0dc29, checked in by Stefano Rivera <stefano@…>, 8 years ago

Refactored as classes for readability and to make it easier to support more types

File size: 4.4 KB
RevLine 
[89]1'''
2Serializer and dumper for a simple, YAMLish format (actually a YAML subset).
3The top level object is a dict.
4lists and dicts can contain lists, dicts, and strings.
5'''
6
7import re
8
9
10def dump(data, file_object):
[111]11 file_object.write(dump_s(data))
[90]12
13
[111]14def dump_s(data):
15 return Dumper().dump(data)
[89]16
17
18def load(file_object):
[111]19 yaml = file_object.read()
20 return load_s(yaml)
21
22
23def load_s(yaml):
24 return Parser().parse(yaml)
25
26
27
28class Dumper(object):
29 def dump(self, data):
30 return '\n'.join(self._dump(data))
31
32 def _dump(self, data, indent=0):
33 for type_ in (list, dict, basestring):
34 if isinstance(data, type_):
35 f = getattr(self, '_dump_%s' % type_.__name__)
36 return f(data, indent)
37 raise NotImplementedError()
38
39 def _dump_list(self, data, indent):
40 output = []
41 for item in data:
42 dumped = self._dump(item, indent + 2)
43 dumped[0] = '%s- %s' % (' ' * indent, dumped[0][indent + 2:])
44 output += dumped
45 return output
[89]46
[111]47 def _dump_dict(self, data, indent):
48 output = []
49 for k, v in data.iteritems():
50 output.append('%s%s:' % (' ' * indent, k))
51 if isinstance(v, dict):
52 output += self._dump(v, indent + 2)
53 elif isinstance(v, list):
54 output += self._dump(v, indent)
55 else:
56 value = self._dump(v)
57 assert len(value) == 1
58 output[-1] += ' %s' % value[0]
59 return output
60
61 def _dump_basestring(self, data, indent):
62 return [' ' * indent + data]
63
[89]64
[111]65class Parser(object):
66 _spaces_re = re.compile(r'^(\s*)(.*)')
67 _list_re = re.compile(r'^(-\s+)(.*)')
68 _dict_re = re.compile(r'^([^-:]+):\s?(.*)')
[89]69
[111]70 def __init__(self):
71 # Stack of (indent level, container object)
72 self._stack = [(0, {})]
73 # When a dict's value is a nested block, remember the key
74 self._parent_key = None
75
76 @property
77 def _indent(self):
78 return self._stack[-1][0]
79
80 @property
81 def _container(self):
82 return self._stack[-1][1]
[89]83
[111]84 @property
85 def _in_list(self):
86 return isinstance(self._container, list)
87
88 @property
89 def _in_dict(self):
90 return isinstance(self._container, dict)
91
92 def _push(self, container, indent=None):
93 in_list = self._in_list
94 assert in_list or self._parent_key
95
96 if indent is None:
97 indent = self._indent
98 self._stack.append((indent, container()))
99 if in_list:
100 self._stack[-2][1].append(self._container)
101 else:
102 self._stack[-2][1][self._parent_key] = self._container
103 self._parent_key = None
104
105 def parse(self, yaml):
106 for line in yaml.splitlines():
107 spaces, line = self._spaces_re.match(line).groups()
108
109 while len(spaces) < self._indent:
110 self._stack.pop()
111
112 lm = self._list_re.match(line)
113 dm = self._dict_re.match(line)
114 if len(spaces) == self._indent:
115 if lm and self._in_dict:
[89]116 # Starting a list in a dict
[111]117 self._push(list)
118 elif dm and self._in_list:
119 # Left an embedded list
120 self._stack.pop()
121
122 if len(spaces) > self._indent:
123 assert self._parent_key
124 if dm:
125 # Nested dict
126 self._push(dict, len(spaces))
127 elif lm:
128 # Over-indented list in a dict
129 self._push(list, len(spaces))
[89]130
[111]131 indent = self._indent
132 while lm and lm.group(2).startswith('- '):
133 # Nested lists
134 prefix, line = lm.groups()
135 indent += len(prefix)
136 self._push(list, indent)
137 lm = self._list_re.match(line)
138 del indent
139
140 if lm:
141 prefix, line = lm.groups()
142 dm = self._dict_re.match(line)
143 if dm:
144 self._push(dict, self._indent + len(prefix))
145 else:
146 assert self._in_list
147 self._container.append(line)
148
[103]149 if dm:
[111]150 key, value = dm.groups()
151 if value:
152 assert self._in_dict
153 self._container[key] = value
154 else:
155 self._parent_key = key
[89]156
[111]157 return self._stack[0][1]
Note: See TracBrowser for help on using the repository browser.