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 """Configuration variable management for the cr tool. 6 7 This holds the classes that support the hierarchical variable management used 8 in the cr tool to provide all the command configuration controls. 9 """ 10 11 import string 12 13 import cr.visitor 14 15 _PARSE_CONSTANT_VALUES = [None, True, False] 16 _PARSE_CONSTANTS = dict((str(value), value) for value in _PARSE_CONSTANT_VALUES) 17 18 # GLOBALS is the singleton used to tie static global configuration objects 19 # together. 20 GLOBALS = [] 21 22 23 class _MissingToErrorFormatter(string.Formatter): 24 """A string formatter used in value resolve. 25 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. 30 """ 31 32 def convert_field(self, value, conversion): 33 if conversion == 'e': 34 result = str(value) 35 if not result: 36 raise KeyError('unknown') 37 return result 38 return super(_MissingToErrorFormatter, self).convert_field( 39 value, conversion) 40 41 42 class _Tracer(object): 43 """Traces variable lookups. 44 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 48 used in an operation. 49 """ 50 51 def __init__(self, config): 52 self.config = config 53 self.trail = [] 54 55 def __enter__(self): 56 self.config.fixup_hooks.append(self._Trace) 57 return self 58 59 def __exit__(self, *_): 60 self.config.fixup_hooks.remove(self._Trace) 61 self.config.trail = self.trail 62 return False 63 64 def _Trace(self, _, key, value): 65 self.trail.append((key, value)) 66 return value 67 68 69 class Config(cr.visitor.Node): 70 """The main variable holding class. 71 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 76 overrides the rest. 77 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. 82 """ 83 84 @classmethod 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) 88 89 @classmethod 90 def If(cls, condition, true_value, false_value=''): 91 """Returns a config value that selects a value based on the condition. 92 93 Args: 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. 97 Returns: 98 A dynamic value. 99 """ 100 def Resolve(context): 101 test = context.Get(condition) 102 if test: 103 value = true_value 104 else: 105 value = false_value 106 return context.Substitute(value) 107 return Resolve 108 109 @classmethod 110 def Optional(cls, value, alternate=''): 111 """Returns a dynamic value that defaults to an alternate. 112 113 Args: 114 value: The main value to resolve. 115 alternate: The value to use if the main value does not resolve. 116 Returns: 117 value if it resolves, alternate otherwise. 118 """ 119 def Resolve(context): 120 try: 121 return context.Substitute(value) 122 except KeyError: 123 return context.Substitute(alternate) 124 return Resolve 125 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 = [] 131 self.trail = [] 132 133 @property 134 def literal(self): 135 return self._literal 136 137 def Substitute(self, value): 138 return self._formatter.vformat(str(value), (), self) 139 140 def Resolve(self, visitor, key, value): 141 """Resolves a value to it's final form. 142 143 Raw values can be callable, simple values, or contain format strings. 144 Args: 145 visitor: The vistior asking to resolve a value. 146 key: The key being visited. 147 value: The unresolved value associated with the key. 148 Returns: 149 the fully resolved value. 150 """ 151 error = None 152 if callable(value): 153 value = value(self) 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: 157 try: 158 value = self.Substitute(value) 159 except KeyError as e: 160 error = e 161 return self.Fixup(key, value), error 162 163 def Fixup(self, key, value): 164 for hook in self.fixup_hooks: 165 value = hook(self, key, value) 166 return value 167 168 @staticmethod 169 def ParseValue(value): 170 """Converts a string to a value. 171 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 174 special. 175 Args: 176 value: The the string value to interpret. 177 Returns: 178 the parsed form of the value. 179 """ 180 if value in _PARSE_CONSTANTS: 181 return _PARSE_CONSTANTS[value] 182 try: 183 return int(value) 184 except ValueError: 185 pass 186 try: 187 return float(value) 188 except ValueError: 189 pass 190 return value 191 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): 195 return 196 self._values[key] = value 197 self.NotifyChanged() 198 return self 199 200 def ApplyMap(self, arg): 201 for key, value in arg.items(): 202 self._Set(key, value) 203 return self 204 205 def Apply(self, args, kwargs): 206 """Bulk set variables from arguments. 207 208 Intended for internal use by the Set and From methods. 209 Args: 210 args: must be either a dict or something that can build a dict. 211 kwargs: must be a dict. 212 Returns: 213 self for easy chaining. 214 """ 215 if len(args) == 1: 216 arg = args[0] 217 if isinstance(arg, dict): 218 self.ApplyMap(arg) 219 else: 220 self.ApplyMap(dict(arg)) 221 elif len(args) > 1: 222 self.ApplyMap(dict(args)) 223 self.ApplyMap(kwargs) 224 return self 225 226 def Set(self, *args, **kwargs): 227 return self.Apply(args, kwargs) 228 229 def Trace(self): 230 return _Tracer(self) 231 232 def __getitem__(self, key): 233 return self.Get(key) 234 235 def __setitem__(self, key, value): 236 self._Set(key, value) 237 238 def __contains__(self, key): 239 return self.Find(key) is not None 240 241