blob: 8e01c70b5614e5078fefbfd7bc0e0e23c0cea341 [file] [log] [blame]
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""".
"""
import collections
# HIDDEN is a marker used to suppress a value, making it as if it were not set
# in that object. This causes the search to continue through the tree.
# This is most useful as a return value of dynamic values that want to find
# the value they are shadowing.
HIDDEN = object()
class VisitComplete(Exception):
"""Indicates a vist traversal has finished early."""
class Visitor(object):
"""The base class for anything that wants to "visit" all variables.
The two main uses of visitor are search and export. They differ in that export
is trying to find all variables, whereas search is just looking for one.
"""
def __init__(self):
self.stack = []
def VisitNode(self, node):
"""Called for every node in the tree."""
if not node.enabled:
return self
try:
try:
self.stack.append(node)
self.StartNode()
# Visit all the values first
for key in self.KeysOf(node.values):
self.Visit(key, node.values[key])
# And now recurse into all the children
for child in node.children:
self.VisitNode(child)
finally:
self.EndNode()
self.stack.pop()
except VisitComplete:
if self.stack:
# propagate back up the stack
raise
return self
def Visit(self, key, value):
"""Visit is called for every variable in each node."""
def StartNode(self):
"""StartNode is called once for each node before traversal."""
def EndNode(self):
"""Visit is called for every node after traversal."""
@property
def root_node(self):
"""Returns the variable at the root of the current traversal."""
return self.stack[0]
@property
def current_node(self):
"""Returns the node currently being scanned."""
return self.stack[-1]
def Resolve(self, key, value):
"""Returns a fully substituted value.
This asks the root node to do the actual work.
Args:
key: The key being visited.
value: The unresolved value associated with the key.
Returns:
the fully resolved value.
"""
return self.root_node.Resolve(self, key, value)
def Where(self):
"""Returns the current traversal stack as a string."""
return '/'.join([entry.name for entry in self.stack])
class SearchVisitor(Visitor):
"""A Visitor that finds a single matching key."""
def __init__(self, key):
super(SearchVisitor, self).__init__()
self.key = key
self.found = False
self.error = None
def KeysOf(self, store):
if self.key in store:
yield self.key
def Visit(self, key, value):
value, error = self.Resolve(key, value)
if value is not HIDDEN:
self.found = True
self.value = value
self.error = error
raise VisitComplete()
class WhereVisitor(SearchVisitor):
"""A SearchVisitor that returns the path to the matching key."""
def Visit(self, key, value):
self.where = self.Where()
super(WhereVisitor, self).Visit(key, value)
class ExportVisitor(Visitor):
"""A visitor that builds a fully resolved map of all variables."""
def __init__(self, store):
super(ExportVisitor, self).__init__()
self.store = store
def KeysOf(self, store):
if self.current_node.export is False:
# not exporting from this config
return
for key in store.keys():
if key in self.store:
# duplicate
continue
if (self.current_node.export is None) and key.startswith('_'):
# non exported name
continue
yield key
def Visit(self, key, value):
value, _ = self.Resolve(key, value)
if value is not HIDDEN:
self.store[key] = value
class Node(object):
"""The base class for objects in a visitable node tree."""
def __init__(self, name='--', enabled=True, export=True):
self._name = name
self._children = collections.deque()
self._values = {}
self._viewers = []
self.trail = []
self._enabled = enabled
self._export = export
self._export_cache = None
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def enabled(self):
return self._enabled
@enabled.setter
def enabled(self, value):
if self._enabled == value:
return
self._enabled = value
self.NotifyChanged()
@property
def export(self):
return self._export
@property
def exported(self):
if self._export_cache is None:
self._export_cache = ExportVisitor({}).VisitNode(self).store
return self._export_cache
@property
def values(self):
return self._values
@property
def children(self):
return self._children
def RegisterViewer(self, viewer):
self._viewers.append(viewer)
def UnregisterViewer(self, viewer):
self._viewers.remove(viewer)
def OnChanged(self, child):
_ = child
self.NotifyChanged()
def NotifyChanged(self):
self._export_cache = None
for viewers in self._viewers:
viewers.OnChanged(self)
def _AddChild(self, child):
if child and child != self and child not in self._children:
self._children.appendleft(child)
child.RegisterViewer(self)
def AddChild(self, child):
self._AddChild(child)
self.NotifyChanged()
return self
def AddChildren(self, *children):
for child in children:
self._AddChild(child)
self.NotifyChanged()
return self
def Find(self, key):
search = SearchVisitor(key).VisitNode(self)
if not search.found:
return None
return search.value
def WhereIs(self, key):
search = WhereVisitor(key).VisitNode(self)
if not search.found:
return None
return search.where
def Get(self, key, raise_errors=False):
search = SearchVisitor(key).VisitNode(self)
if not search.found:
self.Missing(key)
if search.error and raise_errors:
raise search.error # bad type inference pylint: disable=raising-bad-type
return search.value
def Missing(self, key):
raise KeyError(key)
def Resolve(self, visitor, key, value):
_ = visitor, key
return value
def Wipe(self):
for child in self._children:
child.UnregisterViewer(self)
self._children = collections.deque()
self._values = {}
self.NotifyChanged()