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      raise KeyError(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 Resolve(self, visitor, key, value):
248    _ = visitor, key
249    return value
250
251  def Wipe(self):
252    for child in self._children:
253      child.UnregisterViewer(self)
254    self._children = collections.deque()
255    self._values = {}
256    self.NotifyChanged()
257
258