Add an UMA stat to be able to see if the User pods are show on start screen,
[chromium-blink-merge.git] / components / webui_generator / generator / declaration.py
blobe9863b5803395d29c8f3bda7a4cd863885395752
1 # Copyright 2015 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 import itertools
6 import json
7 import os.path
8 import re
10 import util
12 # Schema is described as follows:
13 # * for simple type:
14 # %simple_type%
15 # * for list:
16 # (list, %items_schema%)
17 # * for dict:
18 # (dict, { '%key_name%': (%is_key_required%, %value_schema%),
19 # ...
20 # })
21 DECLARATION_SCHEMA = (dict, {
22 'imports': (False, (list, unicode)),
23 'type': (True, unicode),
24 'children': (False, (list, (dict, {
25 'id': (True, unicode),
26 'type': (True, unicode),
27 'comment': (False, unicode)
28 }))),
29 'context': (False, (list, (dict, {
30 'name': (True, unicode),
31 'type': (False, unicode),
32 'default': (False, object),
33 'comment': (False, unicode)
34 }))),
35 'events': (False, (list, (dict, {
36 'name': (True, unicode),
37 'comment': (False, unicode)
38 }))),
39 'strings': (False, (list, (dict, {
40 'name': (True, unicode),
41 'comment': (False, unicode)
42 }))),
43 'comment': (False, unicode)
46 # Returns (True,) if |obj| matches |schema|.
47 # Otherwise returns (False, msg), where |msg| is a diagnostic message.
48 def MatchSchema(obj, schema):
49 expected_type = schema[0] if isinstance(schema, tuple) else schema
50 if not isinstance(obj, expected_type):
51 return (False, 'Wrong type, expected %s, got %s.' %
52 (expected_type, type(obj)))
53 if expected_type == dict:
54 obj_keys = set(obj.iterkeys())
55 allowed_keys = set(schema[1].iterkeys())
56 required_keys = set(kv[0] for kv in schema[1].iteritems() if kv[1][0])
57 if not obj_keys.issuperset(required_keys):
58 missing_keys = required_keys.difference(obj_keys)
59 return (False, 'Missing required key%s %s.' %
60 ('s' if len(missing_keys) > 1 else '',
61 ', '.join('\'%s\'' % k for k in missing_keys)))
62 if not obj_keys.issubset(allowed_keys):
63 unknown_keys = obj_keys.difference(allowed_keys)
64 return (False, 'Unknown key%s %s.' %
65 ('s' if len(unknown_keys) > 1 else '',
66 ', '.join('\'%s\'' % k for k in unknown_keys)))
67 for key in obj:
68 match = MatchSchema(obj[key], schema[1][key][1])
69 if not match[0]:
70 return (False, ('[\'%s\'] ' % key) + match[1])
71 elif expected_type == list:
72 for i, item in enumerate(obj):
73 match = MatchSchema(item, schema[1])
74 if not match[0]:
75 return (False, ('[%d] ' % i) + match[1])
76 return (True,)
78 def CheckDeclarationIsValid(declaration):
79 match = MatchSchema(declaration, DECLARATION_SCHEMA)
80 if not match[0]:
81 raise Exception('Declaration is not valid: ' + match[1])
83 def CheckFieldIsUnique(list_of_dicts, field):
84 seen = {}
85 for i, d in enumerate(list_of_dicts):
86 value = d[field]
87 if value in seen:
88 raise Exception(
89 '[%d] Object with "%s" equal to "%s" already exists. See [%d].' %
90 (i, field, value, seen[value]))
91 else:
92 seen[value] = i
94 class FieldDeclaration(object):
95 ALLOWED_TYPES = [
96 'boolean',
97 'integer',
98 'double',
99 'string',
100 'string_list'
103 DEFAULTS = {
104 'boolean': False,
105 'integer': 0,
106 'double': 0.0,
107 'string': u'',
108 'string_list': []
111 def __init__(self, declaration):
112 self.name = declaration['name']
113 self.id = util.ToLowerCamelCase(self.name)
114 self.type = declaration['type'] if 'type' in declaration else 'string'
115 if self.type not in self.ALLOWED_TYPES:
116 raise Exception('Unknown type of context field "%s": "%s"' %
117 (self.name, self.type))
118 self.default_value = declaration['default'] if 'default' in declaration \
119 else self.DEFAULTS[self.type]
120 if type(self.default_value) != type(self.DEFAULTS[self.type]):
121 raise Exception('Wrong type of default for field "%s": '
122 'expected "%s", got "%s"' %
123 (self.name,
124 type(self.DEFAULTS[self.type]),
125 type(self.default_value)))
127 class Declaration(object):
128 class InvalidDeclaration(Exception):
129 def __init__(self, path, message):
130 super(Exception, self).__init__(
131 'Invalid declaration file "%s": %s' % (path, message))
133 class DeclarationsStorage(object):
134 def __init__(self):
135 self.by_path = {}
136 self.by_type = {}
138 def Add(self, declaration):
139 assert declaration.path not in self.by_path
140 self.by_path[declaration.path] = declaration
141 if declaration.type in self.by_type:
142 raise Exception(
143 'Redefinition of type "%s". ' \
144 'Previous definition was in "%s".' % \
145 (declaration.type, self.by_type[declaration.type].path))
146 self.by_type[declaration.type] = declaration
148 def HasPath(self, path):
149 return path in self.by_path
151 def HasType(self, type):
152 return type in self.by_type
154 def GetByPath(self, path):
155 return self.by_path[path]
157 def GetByType(self, type):
158 return self.by_type[type]
160 def GetKnownPathes(self):
161 return self.by_path.keys()
164 def __init__(self, path, known_declarations=None):
165 if known_declarations is None:
166 known_declarations = Declaration.DeclarationsStorage()
167 self.path = path
168 try:
169 self.data = json.load(open(path, 'r'))
170 CheckDeclarationIsValid(self.data)
171 filename_wo_ext = os.path.splitext(os.path.basename(self.path))[0]
172 if self.data['type'] != filename_wo_ext:
173 raise Exception(
174 'File with declaration of type "%s" should be named "%s.json"' %
175 (self.data['type'], filename_wo_ext))
177 known_declarations.Add(self)
178 if 'imports' in self.data:
179 for import_path in self.data['imports']:
180 #TODO(dzhioev): detect circular dependencies.
181 if not known_declarations.HasPath(import_path):
182 Declaration(import_path, known_declarations)
184 for key in ['children', 'strings', 'context', 'events']:
185 if key in self.data:
186 unique_field = 'id' if key == 'children' else 'name'
187 try:
188 CheckFieldIsUnique(self.data[key], unique_field)
189 except Exception as e:
190 raise Exception('["%s"] %s' % (key, e.message))
191 else:
192 self.data[key] = []
194 self.children = {}
195 for child_data in self.data['children']:
196 child_type = child_data['type']
197 child_id = child_data['id']
198 if not known_declarations.HasType(child_type):
199 raise Exception('Unknown type "%s" for child "%s"' %
200 (child_type, child_id))
201 self.children[child_id] = known_declarations.GetByType(child_type)
203 self.fields = [FieldDeclaration(d) for d in self.data['context']]
204 fields_names = [field.name for field in self.fields]
205 if len(fields_names) > len(set(fields_names)):
206 raise Exception('Duplicate fields in declaration.')
207 self.known_declarations = known_declarations
208 except Declaration.InvalidDeclaration:
209 raise
210 except Exception as e:
211 raise Declaration.InvalidDeclaration(self.path, e.message)
213 @property
214 def type(self):
215 return self.data['type']
217 @property
218 def strings(self):
219 return (string['name'] for string in self.data['strings'])
221 @property
222 def events(self):
223 return (event['name'] for event in self.data['events'])
225 @property
226 def webui_view_basename(self):
227 return self.type + '_web_ui_view'
229 @property
230 def webui_view_h_name(self):
231 return self.webui_view_basename + '.h'
233 @property
234 def webui_view_cc_name(self):
235 return self.webui_view_basename + '.cc'
237 @property
238 def webui_view_include_path(self):
239 return os.path.join(os.path.dirname(self.path), self.webui_view_h_name)
241 @property
242 def export_h_name(self):
243 return self.type + '_export.h'
245 @property
246 def component_export_macro(self):
247 return "WUG_" + self.type.upper() + "_EXPORT"
249 @property
250 def component_impl_macro(self):
251 return "WUG_" + self.type.upper() + "_IMPLEMENTATION"
253 @property
254 def export_h_include_path(self):
255 return os.path.join(os.path.dirname(self.path), self.export_h_name)
257 @property
258 def view_model_basename(self):
259 return self.type + '_view_model'
261 @property
262 def view_model_h_name(self):
263 return self.view_model_basename + '.h'
265 @property
266 def view_model_cc_name(self):
267 return self.view_model_basename + '.cc'
269 @property
270 def view_model_include_path(self):
271 return os.path.join(os.path.dirname(self.path), self.view_model_h_name)
273 @property
274 def html_view_basename(self):
275 return '%s-view' % self.type.replace('_', '-')
277 @property
278 def html_view_html_name(self):
279 return self.html_view_basename + '.html'
281 @property
282 def html_view_js_name(self):
283 return self.html_view_basename + '.js'
285 @property
286 def html_view_html_include_path(self):
287 return os.path.join(os.path.dirname(self.path), self.html_view_html_name)
289 @property
290 def html_view_js_include_path(self):
291 return os.path.join(os.path.dirname(self.path), self.html_view_js_name)
293 @property
294 def build_target(self):
295 return self.type + "_wug_generated"
297 @property
298 def webui_view_class(self):
299 return util.ToUpperCamelCase(self.type) + 'WebUIView'
301 @property
302 def view_model_class(self):
303 return util.ToUpperCamelCase(self.type) + 'ViewModel'
305 @property
306 def imports(self):
307 res = set(self.known_declarations.GetKnownPathes())
308 res.remove(self.path)
309 return res