1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""". 6 7""" 8import collections 9 10# HIDDEN is a marker used to suppress a value, making it as if it were not set 11# in that object. This causes the search to continue through the tree. 12# This is most useful as a return value of dynamic values that want to find 13# the value they are shadowing. 14HIDDEN = object() 15 16 17class VisitComplete(Exception): 18 """Indicates a vist traversal has finished early.""" 19 20 21class Visitor(object): 22 """The base class for anything that wants to "visit" all variables. 23 24 The two main uses of visitor are search and export. They differ in that export 25 is trying to find all variables, whereas search is just looking for one. 26 """ 27 28 def __init__(self): 29 self.stack = [] 30 31 def VisitNode(self, node): 32 """Called for every node in the tree.""" 33 if not node.enabled: 34 return self 35 try: 36 try: 37 self.stack.append(node) 38 self.StartNode() 39 # Visit all the values first 40 for key in self.KeysOf(node.values): 41 self.Visit(key, node.values[key]) 42 # And now recurse into all the children 43 for child in node.children: 44 self.VisitNode(child) 45 finally: 46 self.EndNode() 47 self.stack.pop() 48 except VisitComplete: 49 if self.stack: 50 # propagate back up the stack 51 raise 52 return self 53 54 def Visit(self, key, value): 55 """Visit is called for every variable in each node.""" 56 57 def StartNode(self): 58 """StartNode is called once for each node before traversal.""" 59 60 def EndNode(self): 61 """Visit is called for every node after traversal.""" 62 63 @property 64 def root_node(self): 65 """Returns the variable at the root of the current traversal.""" 66 return self.stack[0] 67 68 @property 69 def current_node(self): 70 """Returns the node currently being scanned.""" 71 return self.stack[-1] 72 73 def Resolve(self, key, value): 74 """Returns a fully substituted value. 75 76 This asks the root node to do the actual work. 77 Args: 78 key: The key being visited. 79 value: The unresolved value associated with the key. 80 Returns: 81 the fully resolved value. 82 """ 83 return self.root_node.Resolve(self, key, value) 84 85 def Where(self): 86 """Returns the current traversal stack as a string.""" 87 return '/'.join([entry.name for entry in self.stack]) 88 89 90class SearchVisitor(Visitor): 91 """A Visitor that finds a single matching key.""" 92 93 def __init__(self, key): 94 super(SearchVisitor, self).__init__() 95 self.key = key 96 self.found = False 97 self.error = None 98 99 def KeysOf(self, store): 100 if self.key in store: 101 yield self.key 102 103 def Visit(self, key, value): 104 value, error = self.Resolve(key, value) 105 if value is not HIDDEN: 106 self.found = True 107 self.value = value 108 self.error = error 109 raise VisitComplete() 110 111 112class WhereVisitor(SearchVisitor): 113 """A SearchVisitor that returns the path to the matching key.""" 114 115 def Visit(self, key, value): 116 self.where = self.Where() 117 super(WhereVisitor, self).Visit(key, value) 118 119 120class ExportVisitor(Visitor): 121 """A visitor that builds a fully resolved map of all variables.""" 122 123 def __init__(self, store): 124 super(ExportVisitor, self).__init__() 125 self.store = store 126 127 def KeysOf(self, store): 128 if self.current_node.export is False: 129 # not exporting from this config 130 return 131 for key in store.keys(): 132 if key in self.store: 133 # duplicate 134 continue 135 if (self.current_node.export is None) and key.startswith('_'): 136 # non exported name 137 continue 138 yield key 139 140 def Visit(self, key, value): 141 value, _ = self.Resolve(key, value) 142 if value is not HIDDEN: 143 self.store[key] = value 144 145 146class Node(object): 147 """The base class for objects in a visitable node tree.""" 148 149 def __init__(self, name='--', enabled=True, export=True): 150 self._name = name 151 self._children = collections.deque() 152 self._values = {} 153 self._viewers = [] 154 self.trail = [] 155 self._enabled = enabled 156 self._export = export 157 self._export_cache = None 158 159 @property 160 def name(self): 161 return self._name 162 163 @name.setter 164 def name(self, value): 165 self._name = value 166 167 @property 168 def enabled(self): 169 return self._enabled 170 171 @enabled.setter 172 def enabled(self, value): 173 if self._enabled == value: 174 return 175 self._enabled = value 176 self.NotifyChanged() 177 178 @property 179 def export(self): 180 return self._export 181 182 @property 183 def exported(self): 184 if self._export_cache is None: 185 self._export_cache = ExportVisitor({}).VisitNode(self).store 186 return self._export_cache 187 188 @property 189 def values(self): 190 return self._values 191 192 @property 193 def children(self): 194 return self._children 195 196 def RegisterViewer(self, viewer): 197 self._viewers.append(viewer) 198 199 def UnregisterViewer(self, viewer): 200 self._viewers.remove(viewer) 201 202 def OnChanged(self, child): 203 _ = child 204 self.NotifyChanged() 205 206 def NotifyChanged(self): 207 self._export_cache = None 208 for viewers in self._viewers: 209 viewers.OnChanged(self) 210 211 def _AddChild(self, child): 212 if child and child != self and child not in self._children: 213 self._children.appendleft(child) 214 child.RegisterViewer(self) 215 216 def AddChild(self, child): 217 self._AddChild(child) 218 self.NotifyChanged() 219 return self 220 221 def AddChildren(self, *children): 222 for child in children: 223 self._AddChild(child) 224 self.NotifyChanged() 225 return self 226 227 def Find(self, key): 228 search = SearchVisitor(key).VisitNode(self) 229 if not search.found: 230 return None 231 return search.value 232 233 def WhereIs(self, key): 234 search = WhereVisitor(key).VisitNode(self) 235 if not search.found: 236 return None 237 return search.where 238 239 def Get(self, key, raise_errors=False): 240 search = SearchVisitor(key).VisitNode(self) 241 if not search.found: 242 self.Missing(key) 243 if search.error and raise_errors: 244 raise search.error # bad type inference pylint: disable=raising-bad-type 245 return search.value 246 247 def Missing(self, key): 248 raise KeyError(key) 249 250 def Resolve(self, visitor, key, value): 251 _ = visitor, key 252 return value 253 254 def Wipe(self): 255 for child in self._children: 256 child.UnregisterViewer(self) 257 self._children = collections.deque() 258 self._values = {} 259 self.NotifyChanged() 260 261