Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / tools / cr / cr / config.py
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(base):
101       test = base.Get(condition)
102       if test:
103         value = true_value
104       else:
105         value = false_value
106       return base.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(base):
120       try:
121         return base.Substitute(value)
122       except KeyError:
123         return base.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