OLD | NEW |
| (Empty) |
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. | |
4 | |
5 import itertools | |
6 import json | |
7 import os.path | |
8 import re | |
9 | |
10 import util | |
11 | |
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) | |
44 }) | |
45 | |
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,) | |
77 | |
78 def CheckDeclarationIsValid(declaration): | |
79 match = MatchSchema(declaration, DECLARATION_SCHEMA) | |
80 if not match[0]: | |
81 raise Exception('Declaration is not valid: ' + match[1]) | |
82 | |
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 | |
93 | |
94 class FieldDeclaration(object): | |
95 ALLOWED_TYPES = [ | |
96 'boolean', | |
97 'integer', | |
98 'double', | |
99 'string', | |
100 'string_list' | |
101 ] | |
102 | |
103 DEFAULTS = { | |
104 'boolean': False, | |
105 'integer': 0, | |
106 'double': 0.0, | |
107 'string': u'', | |
108 'string_list': [] | |
109 } | |
110 | |
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))) | |
126 | |
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)) | |
132 | |
133 class DeclarationsStorage(object): | |
134 def __init__(self): | |
135 self.by_path = {} | |
136 self.by_type = {} | |
137 | |
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 | |
147 | |
148 def HasPath(self, path): | |
149 return path in self.by_path | |
150 | |
151 def HasType(self, type): | |
152 return type in self.by_type | |
153 | |
154 def GetByPath(self, path): | |
155 return self.by_path[path] | |
156 | |
157 def GetByType(self, type): | |
158 return self.by_type[type] | |
159 | |
160 def GetKnownPathes(self): | |
161 return self.by_path.keys() | |
162 | |
163 | |
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)) | |
176 | |
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) | |
183 | |
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] = [] | |
193 | |
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) | |
202 | |
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) | |
212 | |
213 @property | |
214 def type(self): | |
215 return self.data['type'] | |
216 | |
217 @property | |
218 def strings(self): | |
219 return (string['name'] for string in self.data['strings']) | |
220 | |
221 @property | |
222 def events(self): | |
223 return (event['name'] for event in self.data['events']) | |
224 | |
225 @property | |
226 def webui_view_basename(self): | |
227 return self.type + '_web_ui_view' | |
228 | |
229 @property | |
230 def webui_view_h_name(self): | |
231 return self.webui_view_basename + '.h' | |
232 | |
233 @property | |
234 def webui_view_cc_name(self): | |
235 return self.webui_view_basename + '.cc' | |
236 | |
237 @property | |
238 def webui_view_include_path(self): | |
239 return os.path.join(os.path.dirname(self.path), self.webui_view_h_name) | |
240 | |
241 @property | |
242 def export_h_name(self): | |
243 return self.type + '_export.h' | |
244 | |
245 @property | |
246 def component_export_macro(self): | |
247 return "WUG_" + self.type.upper() + "_EXPORT" | |
248 | |
249 @property | |
250 def component_impl_macro(self): | |
251 return "WUG_" + self.type.upper() + "_IMPLEMENTATION" | |
252 | |
253 @property | |
254 def export_h_include_path(self): | |
255 return os.path.join(os.path.dirname(self.path), self.export_h_name) | |
256 | |
257 @property | |
258 def view_model_basename(self): | |
259 return self.type + '_view_model' | |
260 | |
261 @property | |
262 def view_model_h_name(self): | |
263 return self.view_model_basename + '.h' | |
264 | |
265 @property | |
266 def view_model_cc_name(self): | |
267 return self.view_model_basename + '.cc' | |
268 | |
269 @property | |
270 def view_model_include_path(self): | |
271 return os.path.join(os.path.dirname(self.path), self.view_model_h_name) | |
272 | |
273 @property | |
274 def html_view_basename(self): | |
275 return '%s-view' % self.type.replace('_', '-') | |
276 | |
277 @property | |
278 def html_view_html_name(self): | |
279 return self.html_view_basename + '.html' | |
280 | |
281 @property | |
282 def html_view_js_name(self): | |
283 return self.html_view_basename + '.js' | |
284 | |
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) | |
288 | |
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) | |
292 | |
293 @property | |
294 def build_target(self): | |
295 return self.type + "_wug_generated" | |
296 | |
297 @property | |
298 def webui_view_class(self): | |
299 return util.ToUpperCamelCase(self.type) + 'WebUIView' | |
300 | |
301 @property | |
302 def view_model_class(self): | |
303 return util.ToUpperCamelCase(self.type) + 'ViewModel' | |
304 | |
305 @property | |
306 def imports(self): | |
307 res = set(self.known_declarations.GetKnownPathes()) | |
308 res.remove(self.path) | |
309 return res | |
310 | |
311 | |
312 | |
OLD | NEW |