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
Line 
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):
11 file_object.write(dump_s(data))
12
13
14def dump_s(data):
15 return Dumper().dump(data)
16
17
18def load(file_object):
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
46
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
64
65class Parser(object):
66 _spaces_re = re.compile(r'^(\s*)(.*)')
67 _list_re = re.compile(r'^(-\s+)(.*)')
68 _dict_re = re.compile(r'^([^-:]+):\s?(.*)')
69
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]
83
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:
116 # Starting a list in a dict
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))
130
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
149 if dm:
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
156
157 return self._stack[0][1]
Note: See TracBrowser for help on using the repository browser.