| # Contributors Listed Below - COPYRIGHT 2016 |
| # [+] International Business Machines Corp. |
| # |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| # implied. See the License for the specific language governing |
| # permissions and limitations under the License. |
| |
| |
| class PathTreeItemIterator(object): |
| def __init__(self, path_tree, subtree, depth): |
| self.path_tree = path_tree |
| self.path = [] |
| self.itlist = [] |
| self.subtree = ['/'] + filter(bool, subtree.split('/')) |
| self.depth = depth |
| d = path_tree.root |
| for k in self.subtree: |
| try: |
| d = d[k]['children'] |
| except KeyError: |
| raise KeyError(subtree) |
| self.it = d.iteritems() |
| |
| def __iter__(self): |
| return self |
| |
| def __next__(self): |
| return super(PathTreeItemIterator, self).next() |
| |
| def next(self): |
| key, value = self._next() |
| path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path) |
| return path, value.get('data') |
| |
| def _next(self): |
| try: |
| while True: |
| x = self.it.next() |
| depth_exceeded = len(self.path) + 1 > self.depth |
| if self.depth and depth_exceeded: |
| continue |
| self.itlist.append(self.it) |
| self.path.append(x[0]) |
| self.it = x[1]['children'].iteritems() |
| break |
| |
| except StopIteration: |
| if not self.itlist: |
| raise StopIteration |
| |
| self.it = self.itlist.pop() |
| self.path.pop() |
| x = self._next() |
| |
| return x |
| |
| |
| class PathTreeKeyIterator(PathTreeItemIterator): |
| def __init__(self, path_tree, subtree, depth): |
| super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth) |
| |
| def next(self): |
| return super(PathTreeKeyIterator, self).next()[0] |
| |
| |
| class PathTree: |
| def __init__(self): |
| self.root = {} |
| |
| def _try_delete_parent(self, elements): |
| if len(elements) == 1: |
| return False |
| |
| kids = 'children' |
| elements.pop() |
| d = self.root |
| for k in elements[:-1]: |
| d = d[k][kids] |
| |
| if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]: |
| del d[elements[-1]] |
| self._try_delete_parent(elements) |
| |
| def _get_node(self, key): |
| kids = 'children' |
| elements = ['/'] + filter(bool, key.split('/')) |
| d = self.root |
| for k in elements[:-1]: |
| try: |
| d = d[k][kids] |
| except KeyError: |
| raise KeyError(key) |
| |
| return d[elements[-1]] |
| |
| def __iter__(self): |
| return self |
| |
| def __missing__(self, key): |
| for x in self.iterkeys(): |
| if key == x: |
| return False |
| return True |
| |
| def __delitem__(self, key): |
| kids = 'children' |
| elements = ['/'] + filter(bool, key.split('/')) |
| d = self.root |
| for k in elements[:-1]: |
| try: |
| d = d[k][kids] |
| except KeyError: |
| raise KeyError(key) |
| |
| del d[elements[-1]] |
| self._try_delete_parent(elements) |
| |
| def __setitem__(self, key, value): |
| kids = 'children' |
| elements = ['/'] + filter(bool, key.split('/')) |
| d = self.root |
| for k in elements[:-1]: |
| d = d.setdefault(k, {kids: {}})[kids] |
| |
| children = d.setdefault(elements[-1], {kids: {}})[kids] |
| d[elements[-1]].update({kids: children, 'data': value}) |
| |
| def __getitem__(self, key): |
| return self._get_node(key).get('data') |
| |
| def setdefault(self, key, default): |
| if not self.get(key): |
| self.__setitem__(key, default) |
| |
| return self.__getitem__(key) |
| |
| def get(self, key, default=None): |
| try: |
| x = self.__getitem__(key) |
| except KeyError: |
| x = default |
| |
| return x |
| |
| def get_children(self, key): |
| return [x for x in self._get_node(key)['children'].iterkeys()] |
| |
| def demote(self, key): |
| n = self._get_node(key) |
| if 'data' in n: |
| del n['data'] |
| |
| def keys(self, subtree='/', depth=None): |
| return [x for x in self.iterkeys(subtree, depth)] |
| |
| def values(self, subtree='/', depth=None): |
| return [x[1] for x in self.iteritems(subtree, depth)] |
| |
| def items(self, subtree='/', depth=None): |
| return [x for x in self.iteritems(subtree, depth)] |
| |
| def dataitems(self, subtree='/', depth=None): |
| return [x for x in self.iteritems(subtree, depth) |
| if x[1] is not None] |
| |
| def iterkeys(self, subtree='/', depth=None): |
| if not self.root: |
| return {}.iterkeys() |
| return PathTreeKeyIterator(self, subtree, depth) |
| |
| def iteritems(self, subtree='/', depth=None): |
| if not self.root: |
| return {}.iteritems() |
| return PathTreeItemIterator(self, subtree, depth) |
| |
| def dumpd(self, subtree='/'): |
| result = {} |
| d = result |
| |
| for k, v in self.iteritems(subtree): |
| elements = ['/'] + filter(bool, k.split('/')) |
| d = result |
| for k in elements: |
| d = d.setdefault(k, {}) |
| if v is not None: |
| d.update(v) |
| |
| return result |