from contoml.file import toplevels from contoml.file.cascadedict import CascadeDict class NamedDict(dict): """ A dict that can use Name instances as keys. """ def __init__(self, other_dict=None): dict.__init__(self) if other_dict: for k, v in other_dict.items(): self[k] = v def __setitem__(self, key, value): """ key can be an Name instance. When key is a path in the form of an Name instance, all the parents and grandparents of the value are created along the way as instances of NamedDict. If the parent of the value exists, it is replaced with a CascadeDict() that cascades the old parent value with a new NamedDict that contains the given child name and value. """ if isinstance(key, toplevels.Name): if len(key.sub_names) == 1: name = key.sub_names[0] if name in self: self[name] = CascadeDict(self[name], value) else: self[name] = value elif len(key.sub_names) > 1: name = key.sub_names[0] rest_of_key = key.drop(1) if name in self: named_dict = NamedDict() named_dict[rest_of_key] = value self[name] = CascadeDict(self[name], named_dict) else: self[name] = NamedDict() self[name][rest_of_key] = value else: return dict.__setitem__(self, key, value) def __contains__(self, item): try: _ = self[item] return True except KeyError: return False def append(self, key, value): """ Makes sure the value pointed to by key exists and is a list and appends the given value to it. """ if key in self: self[key].append(value) else: self[key] = [value] def __getitem__(self, item): if isinstance(item, toplevels.Name): d = self for name in item.sub_names: d = d[name] return d else: return dict.__getitem__(self, item) def structure(table_toplevels): """ Accepts an ordered sequence of TopLevel instances and returns a navigable object structure representation of the TOML file. """ table_toplevels = tuple(table_toplevels) obj = NamedDict() last_array_of_tables = None # The Name of the last array-of-tables header for toplevel in table_toplevels: if isinstance(toplevel, toplevels.AnonymousTable): obj[''] = toplevel.table_element elif isinstance(toplevel, toplevels.Table): if last_array_of_tables and toplevel.name.is_prefixed_with(last_array_of_tables): seq = obj[last_array_of_tables] unprefixed_name = toplevel.name.without_prefix(last_array_of_tables) seq[-1] = CascadeDict(seq[-1], NamedDict({unprefixed_name: toplevel.table_element})) else: obj[toplevel.name] = toplevel.table_element else: # It's an ArrayOfTables if last_array_of_tables and toplevel.name != last_array_of_tables and \ toplevel.name.is_prefixed_with(last_array_of_tables): seq = obj[last_array_of_tables] unprefixed_name = toplevel.name.without_prefix(last_array_of_tables) if unprefixed_name in seq[-1]: seq[-1][unprefixed_name].append(toplevel.table_element) else: cascaded_with = NamedDict({unprefixed_name: [toplevel.table_element]}) seq[-1] = CascadeDict(seq[-1], cascaded_with) else: obj.append(toplevel.name, toplevel.table_element) last_array_of_tables = toplevel.name return obj