1# Contributors Listed Below - COPYRIGHT 2016 2# [+] International Business Machines Corp. 3# 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 14# implied. See the License for the specific language governing 15# permissions and limitations under the License. 16 17 18class PathTreeItemIterator(object): 19 def __init__(self, path_tree, subtree, depth): 20 self.path_tree = path_tree 21 self.path = [] 22 self.itlist = [] 23 self.subtree = ['/'] + list(filter(bool, subtree.split('/'))) 24 self.depth = depth 25 d = path_tree.root 26 for k in self.subtree: 27 try: 28 d = d[k]['children'] 29 except KeyError: 30 raise KeyError(subtree) 31 # TODO: openbmc/openbmc#2994 remove python 2 support 32 try: # python 2 33 self.it = d.iteritems() 34 except AttributeError: # python 3 35 self.it = iter(d.items()) 36 37 def __iter__(self): 38 return self 39 40 # TODO: openbmc/openbmc#2994 remove python 2 support 41 # python 2 42 def next(self): 43 key, value = self._next() 44 path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path) 45 return path, value.get('data') 46 47 # python 3 48 import sys 49 if sys.version_info[0] > 2: 50 __next__ = next 51 52 def _next(self): 53 try: 54 while True: 55 x = next(self.it) 56 depth_exceeded = len(self.path) + 1 > self.depth 57 if self.depth and depth_exceeded: 58 continue 59 self.itlist.append(self.it) 60 self.path.append(x[0]) 61 # TODO: openbmc/openbmc#2994 remove python 2 support 62 try: # python 2 63 self.it = x[1]['children'].iteritems() 64 except AttributeError: # python 3 65 self.it = iter(x[1]['children'].items()) 66 break 67 68 except StopIteration: 69 if not self.itlist: 70 raise StopIteration 71 72 self.it = self.itlist.pop() 73 self.path.pop() 74 x = self._next() 75 76 return x 77 78 79class PathTreeKeyIterator(PathTreeItemIterator): 80 def __init__(self, path_tree, subtree, depth): 81 super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth) 82 83 # TODO: openbmc/openbmc#2994 remove python 2 support 84 # python 2 85 def next(self): 86 return super(PathTreeKeyIterator, self).next()[0] 87 88 # python 3 89 import sys 90 if sys.version_info[0] > 2: 91 __next__ = next 92 93 94class PathTree: 95 def __init__(self): 96 self.root = {} 97 98 def _try_delete_parent(self, elements): 99 if len(elements) == 1: 100 return False 101 102 kids = 'children' 103 elements.pop() 104 d = self.root 105 for k in elements[:-1]: 106 d = d[k][kids] 107 108 if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]: 109 del d[elements[-1]] 110 self._try_delete_parent(elements) 111 112 def _get_node(self, key): 113 kids = 'children' 114 elements = ['/'] + list(filter(bool, key.split('/'))) 115 d = self.root 116 for k in elements[:-1]: 117 try: 118 d = d[k][kids] 119 except KeyError: 120 raise KeyError(key) 121 122 return d[elements[-1]] 123 124 def __iter__(self): 125 return self 126 127 def __missing__(self, key): 128 for x in self.iterkeys(): 129 if key == x: 130 return False 131 return True 132 133 def __delitem__(self, key): 134 kids = 'children' 135 elements = ['/'] + list(filter(bool, key.split('/'))) 136 d = self.root 137 for k in elements[:-1]: 138 try: 139 d = d[k][kids] 140 except KeyError: 141 raise KeyError(key) 142 143 del d[elements[-1]] 144 self._try_delete_parent(elements) 145 146 def __setitem__(self, key, value): 147 kids = 'children' 148 elements = ['/'] + list(filter(bool, key.split('/'))) 149 d = self.root 150 for k in elements[:-1]: 151 d = d.setdefault(k, {kids: {}})[kids] 152 153 children = d.setdefault(elements[-1], {kids: {}})[kids] 154 d[elements[-1]].update({kids: children, 'data': value}) 155 156 def __getitem__(self, key): 157 return self._get_node(key).get('data') 158 159 def setdefault(self, key, default): 160 if not self.get(key): 161 self.__setitem__(key, default) 162 163 return self.__getitem__(key) 164 165 def get(self, key, default=None): 166 try: 167 x = self.__getitem__(key) 168 except KeyError: 169 x = default 170 171 return x 172 173 def get_children(self, key): 174 return [x for x in self._get_node(key)['children'].keys()] 175 176 def demote(self, key): 177 n = self._get_node(key) 178 if 'data' in n: 179 del n['data'] 180 181 def keys(self, subtree='/', depth=None): 182 return [x for x in self.iterkeys(subtree, depth)] 183 184 def values(self, subtree='/', depth=None): 185 return [x[1] for x in self.iteritems(subtree, depth)] 186 187 def items(self, subtree='/', depth=None): 188 return [x for x in self.iteritems(subtree, depth)] 189 190 def dataitems(self, subtree='/', depth=None): 191 return [x for x in self.iteritems(subtree, depth) 192 if x[1] is not None] 193 194 def iterkeys(self, subtree='/', depth=None): 195 if not self.root: 196 # TODO: openbmc/openbmc#2994 remove python 2 support 197 try: # python 2 198 return {}.iterkeys() 199 except AttributeError: # python 3 200 return iter({}.keys()) 201 return PathTreeKeyIterator(self, subtree, depth) 202 203 def iteritems(self, subtree='/', depth=None): 204 if not self.root: 205 # TODO: openbmc/openbmc#2994 remove python 2 support 206 try: # python 2 207 return {}.iteritems() 208 except AttributeError: # python 3 209 return iter({}.items()) 210 return PathTreeItemIterator(self, subtree, depth) 211 212 def dumpd(self, subtree='/'): 213 result = {} 214 d = result 215 216 for k, v in self.iteritems(subtree): 217 elements = ['/'] + list(filter(bool, k.split('/'))) 218 d = result 219 for k in elements: 220 d = d.setdefault(k, {}) 221 if v is not None: 222 d.update(v) 223 224 return result 225