Mercurial > nagslang
view nagslang/yamlish.py @ 120:e6e7a471146a
Support numeric types
author | Stefano Rivera <stefano@rivera.za.net> |
---|---|
date | Mon, 02 Sep 2013 15:31:19 +0200 |
parents | b75de48a618c |
children | 7d022648aa4e |
line wrap: on
line source
''' Serializer and dumper for a simple, YAMLish format (actually a YAML subset). The top level object is a dict. lists and dicts can contain: * lists, dicts, * single line strings, * ints, floats, * True, False, and None ''' import re def dump(data, file_object): file_object.write(dump_s(data)) def dump_s(data): return Dumper().dump(data) def load(file_object): yaml = file_object.read() return load_s(yaml) def load_s(yaml): return Parser().parse(yaml.strip()) class Dumper(object): def dump(self, data): return '\n'.join(self._dump(data)) def _dump(self, data, indent=0): for type_ in (list, dict, basestring, int, float): if isinstance(data, type_): f = getattr(self, '_dump_%s' % type_.__name__) return f(data, indent) if data in (True, False, None): return self._dump_literal(data, indent) raise NotImplementedError() def _dump_list(self, data, indent): output = [] for item in data: dumped = self._dump(item, indent + 2) dumped[0] = '%s- %s' % (' ' * indent, dumped[0][indent + 2:]) output += dumped return output def _dump_dict(self, data, indent): output = [] for k, v in data.iteritems(): output.append('%s%s:' % (' ' * indent, k)) if isinstance(v, dict): output += self._dump(v, indent + 2) elif isinstance(v, list): output += self._dump(v, indent) else: value = self._dump(v) assert len(value) == 1 output[-1] += ' %s' % value[0] return output def _dump_basestring(self, data, indent): if data in ('true', 'false', 'null'): data = "'%s'" % data return [' ' * indent + data] def _dump_int(self, data, indent): return ['%s%i' % (' ' * indent, data)] def _dump_float(self, data, indent): return ['%s%f' % (' ' * indent, data)] def _dump_literal(self, data, indent): string = { True: 'true', False: 'false', None: 'null', }[data] return [' ' * indent + string] class Parser(object): _spaces_re = re.compile(r'^(\s*)(.*)') _list_re = re.compile(r'^(-\s+)(.*)') _dict_re = re.compile(r'^([^-:]+):\s?(.*)') def __init__(self): # Stack of (indent level, container object) self._stack = [(0, {})] # When a dict's value is a nested block, remember the key self._parent_key = None @property def _indent(self): return self._stack[-1][0] @property def _container(self): return self._stack[-1][1] @property def _in_list(self): return isinstance(self._container, list) @property def _in_dict(self): return isinstance(self._container, dict) def _push(self, container, indent=None): in_list = self._in_list assert in_list or self._parent_key if indent is None: indent = self._indent self._stack.append((indent, container())) if in_list: self._stack[-2][1].append(self._container) else: self._stack[-2][1][self._parent_key] = self._container self._parent_key = None def parse(self, yaml): for line in yaml.splitlines(): spaces, line = self._spaces_re.match(line).groups() while len(spaces) < self._indent: self._stack.pop() lm = self._list_re.match(line) dm = self._dict_re.match(line) if len(spaces) == self._indent: if lm and self._in_dict: # Starting a list in a dict self._push(list) elif dm and self._in_list: # Left an embedded list self._stack.pop() if len(spaces) > self._indent: assert self._parent_key if dm: # Nested dict self._push(dict, len(spaces)) elif lm: # Over-indented list in a dict self._push(list, len(spaces)) indent = self._indent while lm and lm.group(2).startswith('- '): # Nested lists prefix, line = lm.groups() indent += len(prefix) self._push(list, indent) lm = self._list_re.match(line) del indent if lm: prefix, line = lm.groups() dm = self._dict_re.match(line) if dm: self._push(dict, self._indent + len(prefix)) else: assert self._in_list self._container.append(self._parse_value(line)) if dm: key, value = dm.groups() assert self._in_dict if value: self._container[key] = self._parse_value(value) else: self._parent_key = key return self._stack[0][1] def _parse_value(self, value): if value.startswith("'") and value.endswith("'"): return value[1:-1] if value == 'true': return True if value == 'false': return False if value == 'null': return None for type_ in (int, float): try: return type_(value) except ValueError: pass return value