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.
5 """Configuration variable management for the cr tool.
7 This holds the classes that support the hierarchical variable management used
8 in the cr tool to provide all the command configuration controls.
15 _PARSE_CONSTANT_VALUES = [None, True, False]
16 _PARSE_CONSTANTS = dict((str(value), value) for value in _PARSE_CONSTANT_VALUES)
18 # GLOBALS is the singleton used to tie static global configuration objects
23 class _MissingToErrorFormatter(string.Formatter):
24 """A string formatter used in value resolve.
26 The main extra it adds is a new conversion specifier 'e' that throws a
27 KeyError if it could not find the value.
28 This allows a string value to use {A_KEY!e} to indicate that it is a
29 formatting error if A_KEY is not present.
32 def convert_field(self, value, conversion):
36 raise KeyError('unknown')
38 return super(_MissingToErrorFormatter, self).convert_field(
42 class _Tracer(object):
43 """Traces variable lookups.
45 This adds a hook to a config object, and uses it to track all variable
46 lookups that happen and add them to a trail. When done, it removes the hook
47 again. This is used to provide debugging information about what variables are
51 def __init__(self, config):
56 self.config.fixup_hooks.append(self._Trace)
59 def __exit__(self, *_):
60 self.config.fixup_hooks.remove(self._Trace)
61 self.config.trail = self.trail
64 def _Trace(self, _, key, value):
65 self.trail.append((key, value))
69 class Config(cr.visitor.Node):
70 """The main variable holding class.
72 This holds a set of unresolved key value pairs, and the set of child Config
73 objects that should be referenced when looking up a key.
74 Key search is one in a pre-order traversal, and new children are prepended.
75 This means parents override children, and the most recently added child
78 Values can be simple python types, callable dynamic values, or strings.
79 If the value is a string, it is assumed to be a standard python format string
80 where the root config object is used to resolve the keys. This allows values
81 to refer to variables that are overriden in another part of the hierarchy.
85 def From(cls, *args, **kwargs):
86 """Builds an unnamed config object from a set of key,value args."""
87 return Config('??').Apply(args, kwargs)
90 def If(cls, condition, true_value, false_value=''):
91 """Returns a config value that selects a value based on the condition.
94 condition: The variable name to select a value on.
95 true_value: The value to use if the variable is True.
96 false_value: The value to use if the resolved variable is False.
101 test = base.Get(condition)
106 return base.Substitute(value)
110 def Optional(cls, value, alternate=''):
111 """Returns a dynamic value that defaults to an alternate.
114 value: The main value to resolve.
115 alternate: The value to use if the main value does not resolve.
117 value if it resolves, alternate otherwise.
121 return base.Substitute(value)
123 return base.Substitute(alternate)
126 def __init__(self, name='--', literal=False, export=None, enabled=True):
127 super(Config, self).__init__(name=name, enabled=enabled, export=export)
128 self._literal = literal
129 self._formatter = _MissingToErrorFormatter()
130 self.fixup_hooks = []
137 def Substitute(self, value):
138 return self._formatter.vformat(str(value), (), self)
140 def Resolve(self, visitor, key, value):
141 """Resolves a value to it's final form.
143 Raw values can be callable, simple values, or contain format strings.
145 visitor: The vistior asking to resolve a value.
146 key: The key being visited.
147 value: The unresolved value associated with the key.
149 the fully resolved value.
154 # Using existence of value.swapcase as a proxy for is a string
155 elif hasattr(value, 'swapcase'):
156 if not visitor.current_node.literal:
158 value = self.Substitute(value)
159 except KeyError as e:
161 return self.Fixup(key, value), error
163 def Fixup(self, key, value):
164 for hook in self.fixup_hooks:
165 value = hook(self, key, value)
169 def ParseValue(value):
170 """Converts a string to a value.
172 Takes a string from something like an environment variable, and tries to
173 build an internal typed value. Recognizes Null, booleans, and numbers as
176 value: The the string value to interpret.
178 the parsed form of the value.
180 if value in _PARSE_CONSTANTS:
181 return _PARSE_CONSTANTS[value]
192 def _Set(self, key, value):
193 # early out if the value did not change, so we don't call change callbacks
194 if value == self._values.get(key, None):
196 self._values[key] = value
200 def ApplyMap(self, arg):
201 for key, value in arg.items():
202 self._Set(key, value)
205 def Apply(self, args, kwargs):
206 """Bulk set variables from arguments.
208 Intended for internal use by the Set and From methods.
210 args: must be either a dict or something that can build a dict.
211 kwargs: must be a dict.
213 self for easy chaining.
217 if isinstance(arg, dict):
220 self.ApplyMap(dict(arg))
222 self.ApplyMap(dict(args))
223 self.ApplyMap(kwargs)
226 def Set(self, *args, **kwargs):
227 return self.Apply(args, kwargs)
232 def __getitem__(self, key):
235 def __setitem__(self, key, value):
236 self._Set(key, value)
238 def __contains__(self, key):
239 return self.Find(key) is not None