source: nagslang/yamlish.py@ 119:b75de48a618c

Last change on this file since 119:b75de48a618c was 119:b75de48a618c, checked in by Stefano Rivera <stefano@…>, 7 years ago

Support boolean and None types

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