Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Side by Side Diff: components/wug/generator/declaration.py

Issue 928163002: Initial implementation of WebUI generator (WUG) toolkit. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Comments addressed. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 json
6 import os.path
7 import re
8 import util
9
10 # Schema is described as follows:
11 # * for simple type:
12 # %simple_type%
13 # * for list:
14 # (list, %items_schema%)
15 # * for dict:
16 # (dict, { '%key_name%': (%is_key_required%, %value_schema%),
17 # ...
18 # })
19 DECLARATION_SCHEMA = (dict, {
20 'imports': (False, (list, unicode)),
21 'type': (True, unicode),
22 'children': (False, (list, (dict, {
23 'id': (True, unicode),
24 'type': (True, unicode),
25 'comment': (False, unicode)
26 }))),
27 'context': (False, (list, (dict, {
28 'name': (True, unicode),
29 'type': (False, unicode),
30 'default': (False, object),
31 'comment': (False, unicode)
32 }))),
33 'events': (False, (list, (dict, {
34 'name': (True, unicode),
35 'comment': (False, unicode)
36 }))),
37 'strings': (False, (list, (dict, {
38 'name': (True, unicode),
39 'comment': (False, unicode)
40 }))),
41 'comment': (False, unicode)
42 })
43
44 # Returns (True,) if |obj| matches |schema|.
45 # Otherwise returns (False, msg), where |msg| is a diagnostic message.
46 def MatchSchema(obj, schema):
47 expected_type = schema[0] if isinstance(schema, tuple) else schema
48 if not isinstance(obj, expected_type):
49 return (False, 'Wrong type, expected %s, got %s.' %
50 (expected_type, type(obj)))
51 if expected_type == dict:
52 obj_keys = set(obj.iterkeys())
53 allowed_keys = set(schema[1].iterkeys())
54 required_keys = set(kv[0] for kv in schema[1].iteritems() if kv[1][0])
55 if not obj_keys.issuperset(required_keys):
56 missing_keys = required_keys.difference(obj_keys)
57 return (False, 'Missing required key%s %s.' %
58 ('s' if len(missing_keys) > 1 else '',
59 ', '.join('\'%s\'' % k for k in missing_keys)))
60 if not obj_keys.issubset(allowed_keys):
61 unknown_keys = obj_keys.difference(allowed_keys)
62 return (False, 'Unknown key%s %s.' %
63 ('s' if len(unknown_keys) > 1 else '',
64 ', '.join('\'%s\'' % k for k in unknown_keys)))
65 for key in obj:
66 match = MatchSchema(obj[key], schema[1][key][1])
67 if not match[0]:
68 return (False, ('[\'%s\'] ' % key) + match[1])
69 elif expected_type == list:
70 for i, item in enumerate(obj):
71 match = MatchSchema(item, schema[1])
72 if not match[0]:
73 return (False, ('[%d] ' % i) + match[1])
74 return (True,)
75
76 def CheckDeclarationIsValid(declaration):
77 match = MatchSchema(declaration, DECLARATION_SCHEMA)
78 if not match[0]:
79 raise Exception('Declaration is not valid: ' + match[1])
80
81
82 class FieldDeclaration(object):
83 ALLOWED_TYPES = [
84 'boolean',
85 'integer',
86 'double',
87 'string',
88 'string_list'
89 ]
90
91 DEFAULTS = {
92 'boolean': False,
93 'integer': 0,
94 'double': 0.0,
95 'string': u'',
96 'string_list': []
97 }
98
99 def __init__(self, declaration):
100 self.name = declaration['name']
101 self.id = util.ToLowerCamelCase(self.name)
102 self.type = declaration['type'] if 'type' in declaration else 'string'
103 if self.type not in self.ALLOWED_TYPES:
104 raise Exception('Unknown type of context field "%s": "%s"' %
105 (self.name, self.type))
106 self.default_value = declaration['default'] if 'default' in declaration \
107 else self.DEFAULTS[self.type]
108 if type(self.default_value) != type(self.DEFAULTS[self.type]):
109 raise Exception('Wrong type of default for field "%s": '
110 'expected "%s", got "%s"' %
111 (self.name,
112 type(self.DEFAULTS[self.type]),
113 type(self.default_value)))
114
115 class Declaration(object):
116 class InvalidDeclaration(Exception):
117 def __init__(self, path, message):
118 super(Exception, self).__init__(
119 'Invalid declaration file "%s": %s' % (path, message))
120
121 class DeclarationsStorage(object):
122 def __init__(self):
123 self.by_path = {}
124 self.by_type = {}
125
126 def Add(self, declaration):
127 assert declaration.path not in self.by_path
128 self.by_path[declaration.path] = declaration
129 if declaration.type in self.by_type:
130 raise Exception(
131 'Redefinition of type "%s". ' \
132 'Previous definition was in "%s".' % \
133 (declaration.type, self.by_type[declaration.type].path))
134 self.by_type[declaration.type] = declaration
135
136 def HasPath(self, path):
137 return path in self.by_path
138
139 def HasType(self, type):
140 return type in self.by_type
141
142 def GetByPath(self, path):
143 return self.by_path[path]
144
145 def GetByType(self, type):
146 return self.by_type[type]
147
148 def GetKnownPathes(self):
149 return self.by_path.keys()
150
151
152 def __init__(self, path, known_declarations=None):
153 if known_declarations is None:
154 known_declarations = Declaration.DeclarationsStorage()
155 self.path = path
156 try:
157 self.data = json.load(open(path, 'r'))
158 CheckDeclarationIsValid(self.data)
159 filename_wo_ext = os.path.splitext(os.path.basename(self.path))[0]
160 if self.data['type'] != filename_wo_ext:
161 raise Exception(
162 'File with declaration of type "%s" should be named "%s.json"' %
163 (self.data['type'], filename_wo_ext))
164
165 known_declarations.Add(self)
166 if 'imports' in self.data:
167 for import_path in self.data['imports']:
168 #TODO(dzhioev): detect circular dependencies.
169 if not known_declarations.HasPath(import_path):
170 Declaration(import_path, known_declarations)
171 self.children = {}
172 if 'children' in self.data:
173 for child_data in self.data['children']:
174 child_type = child_data['type']
175 child_id = child_data['id']
176 if not known_declarations.HasType(child_type):
177 raise Exception('Unknown type "%s" for child "%s"' %
178 (child_type, child_id))
179 self.children[child_id] = known_declarations.GetByType(child_type)
180 if 'strings' not in self.data:
181 self.data['strings'] = {}
182 if 'context' not in self.data:
183 self.data['context'] = []
184 if 'events' not in self.data:
185 self.data['events'] = []
186 self.fields = [FieldDeclaration(d) for d in self.data['context']]
187 fields_names = [field.name for field in self.fields]
188 if len(fields_names) > len(set(fields_names)):
189 raise Exception('Duplicate fields in declaration.')
190 self.known_declarations = known_declarations
191 except Declaration.InvalidDeclaration:
192 raise
193 except Exception as e:
194 raise Declaration.InvalidDeclaration(self.path, e.message)
195
196 @property
197 def type(self):
198 return self.data['type']
199
200 @property
201 def strings(self):
202 return (string['name'] for string in self.data['strings'])
203
204 @property
205 def events(self):
206 return (event['name'] for event in self.data['events'])
207
208 @property
209 def webui_view_basename(self):
210 return self.type + '_web_ui_view'
211
212 @property
213 def webui_view_h_name(self):
214 return self.webui_view_basename + '.h'
215
216 @property
217 def webui_view_cc_name(self):
218 return self.webui_view_basename + '.cc'
219
220 @property
221 def webui_view_include_path(self):
222 return os.path.join(os.path.dirname(self.path), self.webui_view_h_name)
223
224 @property
225 def export_h_name(self):
226 return self.type + '_export.h'
227
228 @property
229 def component_export_macro(self):
230 return "WUG_" + self.type.upper() + "_EXPORT"
231
232 @property
233 def component_impl_macro(self):
234 return "WUG_" + self.type.upper() + "_IMPLEMENTATION"
235
236 @property
237 def export_h_include_path(self):
238 return os.path.join(os.path.dirname(self.path), self.export_h_name)
239
240 @property
241 def view_model_basename(self):
242 return self.type + '_view_model'
243
244 @property
245 def view_model_h_name(self):
246 return self.view_model_basename + '.h'
247
248 @property
249 def view_model_cc_name(self):
250 return self.view_model_basename + '.cc'
251
252 @property
253 def view_model_include_path(self):
254 return os.path.join(os.path.dirname(self.path), self.view_model_h_name)
255
256 @property
257 def html_view_basename(self):
258 return '%s-view' % self.type.replace('_', '-')
259
260 @property
261 def html_view_html_name(self):
262 return self.html_view_basename + '.html'
263
264 @property
265 def html_view_js_name(self):
266 return self.html_view_basename + '.js'
267
268 @property
269 def html_view_html_include_path(self):
270 return os.path.join(os.path.dirname(self.path), self.html_view_html_name)
271
272 @property
273 def html_view_js_include_path(self):
274 return os.path.join(os.path.dirname(self.path), self.html_view_js_name)
275
276 @property
277 def build_target(self):
278 return self.type + "_wug_generated"
279
280 @property
281 def webui_view_class(self):
282 return util.ToUpperCamelCase(self.type) + 'WebUIView'
283
284 @property
285 def view_model_class(self):
286 return util.ToUpperCamelCase(self.type) + 'ViewModel'
287
288 @property
289 def imports(self):
290 res = set(self.known_declarations.GetKnownPathes())
291 res.remove(self.path)
292 return res
293
294
295
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698