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.
12 # Schema is described as follows:
16 # (list, %items_schema%)
18 # (dict, { '%key_name%': (%is_key_required%, %value_schema%),
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)
29 'context': (False, (list, (dict, {
30 'name': (True, unicode),
31 'type': (False, unicode),
32 'default': (False, object),
33 'comment': (False, unicode)
35 'events': (False, (list, (dict, {
36 'name': (True, unicode),
37 'comment': (False, unicode)
39 'strings': (False, (list, (dict, {
40 'name': (True, unicode),
41 'comment': (False, unicode)
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
)))
68 match
= MatchSchema(obj
[key
], schema
[1][key
][1])
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])
75 return (False, ('[%d] ' % i
) + match
[1])
78 def CheckDeclarationIsValid(declaration
):
79 match
= MatchSchema(declaration
, DECLARATION_SCHEMA
)
81 raise Exception('Declaration is not valid: ' + match
[1])
83 def CheckFieldIsUnique(list_of_dicts
, field
):
85 for i
, d
in enumerate(list_of_dicts
):
89 '[%d] Object with "%s" equal to "%s" already exists. See [%d].' %
90 (i
, field
, value
, seen
[value
]))
94 class FieldDeclaration(object):
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"' %
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):
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
:
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()
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
:
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']:
186 unique_field
= 'id' if key
== 'children' else 'name'
188 CheckFieldIsUnique(self
.data
[key
], unique_field
)
189 except Exception as e
:
190 raise Exception('["%s"] %s' % (key
, e
.message
))
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
:
210 except Exception as e
:
211 raise Declaration
.InvalidDeclaration(self
.path
, e
.message
)
215 return self
.data
['type']
219 return (string
['name'] for string
in self
.data
['strings'])
223 return (event
['name'] for event
in self
.data
['events'])
226 def webui_view_basename(self
):
227 return self
.type + '_web_ui_view'
230 def webui_view_h_name(self
):
231 return self
.webui_view_basename
+ '.h'
234 def webui_view_cc_name(self
):
235 return self
.webui_view_basename
+ '.cc'
238 def webui_view_include_path(self
):
239 return os
.path
.join(os
.path
.dirname(self
.path
), self
.webui_view_h_name
)
242 def export_h_name(self
):
243 return self
.type + '_export.h'
246 def component_export_macro(self
):
247 return "WUG_" + self
.type.upper() + "_EXPORT"
250 def component_impl_macro(self
):
251 return "WUG_" + self
.type.upper() + "_IMPLEMENTATION"
254 def export_h_include_path(self
):
255 return os
.path
.join(os
.path
.dirname(self
.path
), self
.export_h_name
)
258 def view_model_basename(self
):
259 return self
.type + '_view_model'
262 def view_model_h_name(self
):
263 return self
.view_model_basename
+ '.h'
266 def view_model_cc_name(self
):
267 return self
.view_model_basename
+ '.cc'
270 def view_model_include_path(self
):
271 return os
.path
.join(os
.path
.dirname(self
.path
), self
.view_model_h_name
)
274 def html_view_basename(self
):
275 return '%s-view' % self
.type.replace('_', '-')
278 def html_view_html_name(self
):
279 return self
.html_view_basename
+ '.html'
282 def html_view_js_name(self
):
283 return self
.html_view_basename
+ '.js'
286 def html_view_html_include_path(self
):
287 return os
.path
.join(os
.path
.dirname(self
.path
), self
.html_view_html_name
)
290 def html_view_js_include_path(self
):
291 return os
.path
.join(os
.path
.dirname(self
.path
), self
.html_view_js_name
)
294 def build_target(self
):
295 return self
.type + "_wug_generated"
298 def webui_view_class(self
):
299 return util
.ToUpperCamelCase(self
.type) + 'WebUIView'
302 def view_model_class(self
):
303 return util
.ToUpperCamelCase(self
.type) + 'ViewModel'
307 res
= set(self
.known_declarations
.GetKnownPathes())
308 res
.remove(self
.path
)