source: nagslang/yamlish.py@ 118:c02a99502a90

Last change on this file since 118:c02a99502a90 was 113:163122b3a8e9, checked in by Stefano Rivera <stefano@…>, 8 years ago

Appease the gods of PEP-8

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